mirror of
https://github.com/google/nomulus.git
synced 2025-06-26 14:24:55 +02:00
Merge in latest Google changes
This commit is contained in:
commit
8c39e10dec
157 changed files with 4210 additions and 1466 deletions
130
README.md
130
README.md
|
@ -15,10 +15,10 @@ the Markdown documents in the `docs` directory.
|
||||||
|
|
||||||
When it comes to internet land, ownership flows down the following hierarchy:
|
When it comes to internet land, ownership flows down the following hierarchy:
|
||||||
|
|
||||||
1. [ICANN][icann]
|
1. [ICANN][icann]
|
||||||
2. [Registries][registry] (e.g. Google Registry)
|
2. [Registries][registry] (e.g. Google Registry)
|
||||||
3. [Registrars][registrar] (e.g. Google Domains)
|
3. [Registrars][registrar] (e.g. Google Domains)
|
||||||
4. Registrants (e.g. you)
|
4. Registrants (e.g. you)
|
||||||
|
|
||||||
A registry is any organization that operates an entire top-level domain. For
|
A registry is any organization that operates an entire top-level domain. For
|
||||||
example, Verisign controls all the .COM domains and Affilias controls all the
|
example, Verisign controls all the .COM domains and Affilias controls all the
|
||||||
|
@ -50,9 +50,9 @@ are limited to four minutes and ten megabytes in size. Furthermore, queries and
|
||||||
indexes that span entity groups are always eventually consistent, which means
|
indexes that span entity groups are always eventually consistent, which means
|
||||||
they could take seconds, and very rarely, days to update. While most online
|
they could take seconds, and very rarely, days to update. While most online
|
||||||
services find eventual consistency useful, it is not appropriate for a service
|
services find eventual consistency useful, it is not appropriate for a service
|
||||||
conducting financial exchanges. Therefore Domain Registry has been engineered
|
conducting financial exchanges. Therefore Domain Registry has been engineered to
|
||||||
to employ performance and complexity tradeoffs that allow strong consistency to
|
employ performance and complexity tradeoffs that allow strong consistency to be
|
||||||
be applied throughout the codebase.
|
applied throughout the codebase.
|
||||||
|
|
||||||
Domain Registry has a commit log system. Commit logs are retained in datastore
|
Domain Registry has a commit log system. Commit logs are retained in datastore
|
||||||
for thirty days. They are also streamed to Cloud Storage for backup purposes.
|
for thirty days. They are also streamed to Cloud Storage for backup purposes.
|
||||||
|
@ -63,8 +63,8 @@ order to do restores. Each EPP resource entity also stores a map of its past
|
||||||
mutations with 24-hour granularity. This makes it possible to have point-in-time
|
mutations with 24-hour granularity. This makes it possible to have point-in-time
|
||||||
projection queries with effectively no overhead.
|
projection queries with effectively no overhead.
|
||||||
|
|
||||||
The Registry Data Escrow (RDE) system is also built with reliability in mind.
|
The Registry Data Escrow (RDE) system is also built with reliability in mind. It
|
||||||
It executes on top of App Engine task queues, which can be double-executed and
|
executes on top of App Engine task queues, which can be double-executed and
|
||||||
therefore require operations to be idempotent. RDE isn't idempotent. To work
|
therefore require operations to be idempotent. RDE isn't idempotent. To work
|
||||||
around this, RDE uses datastore transactions to achieve mutual exclusion and
|
around this, RDE uses datastore transactions to achieve mutual exclusion and
|
||||||
serialization. We call this the "Locking Rolling Cursor Pattern." One benefit of
|
serialization. We call this the "Locking Rolling Cursor Pattern." One benefit of
|
||||||
|
@ -94,14 +94,15 @@ proxy listening on port 700. Poll message support is also included.
|
||||||
To supplement EPP, Domain Registry also provides a public API for performing
|
To supplement EPP, Domain Registry also provides a public API for performing
|
||||||
domain availability checks. This service listens on the `/check` path.
|
domain availability checks. This service listens on the `/check` path.
|
||||||
|
|
||||||
* [RFC 5730: EPP](http://tools.ietf.org/html/rfc5730)
|
* [RFC 5730: EPP](http://tools.ietf.org/html/rfc5730)
|
||||||
* [RFC 5731: EPP Domain Mapping](http://tools.ietf.org/html/rfc5731)
|
* [RFC 5731: EPP Domain Mapping](http://tools.ietf.org/html/rfc5731)
|
||||||
* [RFC 5732: EPP Host Mapping](http://tools.ietf.org/html/rfc5732)
|
* [RFC 5732: EPP Host Mapping](http://tools.ietf.org/html/rfc5732)
|
||||||
* [RFC 5733: EPP Contact Mapping](http://tools.ietf.org/html/rfc5733)
|
* [RFC 5733: EPP Contact Mapping](http://tools.ietf.org/html/rfc5733)
|
||||||
* [RFC 3915: EPP Grace Period Mapping](http://tools.ietf.org/html/rfc3915)
|
* [RFC 3915: EPP Grace Period Mapping](http://tools.ietf.org/html/rfc3915)
|
||||||
* [RFC 5734: EPP Transport over TCP](http://tools.ietf.org/html/rfc5734)
|
* [RFC 5734: EPP Transport over TCP](http://tools.ietf.org/html/rfc5734)
|
||||||
* [RFC 5910: EPP DNSSEC Mapping](http://tools.ietf.org/html/rfc5910)
|
* [RFC 5910: EPP DNSSEC Mapping](http://tools.ietf.org/html/rfc5910)
|
||||||
* [Draft: EPP Launch Phase Mapping (Proposed)](http://tools.ietf.org/html/draft-tan-epp-launchphase-11)
|
* [Draft: EPP Launch Phase Mapping (Proposed)]
|
||||||
|
(http://tools.ietf.org/html/draft-tan-epp-launchphase-11)
|
||||||
|
|
||||||
### Registry Data Escrow (RDE)
|
### Registry Data Escrow (RDE)
|
||||||
|
|
||||||
|
@ -114,17 +115,22 @@ This service exists for ICANN regulatory purposes. ICANN needs to know that,
|
||||||
should a registry business ever implode, that they can quickly migrate their
|
should a registry business ever implode, that they can quickly migrate their
|
||||||
TLDs to a different company so that they'll continue to operate.
|
TLDs to a different company so that they'll continue to operate.
|
||||||
|
|
||||||
* [Draft: Registry Data Escrow Specification](http://tools.ietf.org/html/draft-arias-noguchi-registry-data-escrow-06)
|
* [Draft: Registry Data Escrow Specification]
|
||||||
* [Draft: Domain Name Registration Data (DNRD) Objects Mapping](http://tools.ietf.org/html/draft-arias-noguchi-dnrd-objects-mapping-05)
|
(http://tools.ietf.org/html/draft-arias-noguchi-registry-data-escrow-06)
|
||||||
* [Draft: ICANN Registry Interfaces](http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05)
|
* [Draft: Domain Name Registration Data (DNRD) Objects Mapping]
|
||||||
|
(http://tools.ietf.org/html/draft-arias-noguchi-dnrd-objects-mapping-05)
|
||||||
|
* [Draft: ICANN Registry Interfaces]
|
||||||
|
(http://tools.ietf.org/html/draft-lozano-icann-registry-interfaces-05)
|
||||||
|
|
||||||
### Trademark Clearing House (TMCH)
|
### Trademark Clearing House (TMCH)
|
||||||
|
|
||||||
Domain Registry integrates with ICANN and IBM's MarksDB in order to protect
|
Domain Registry integrates with ICANN and IBM's MarksDB in order to protect
|
||||||
trademark holders, when new TLDs are being launched.
|
trademark holders, when new TLDs are being launched.
|
||||||
|
|
||||||
* [Draft: TMCH Functional Spec](http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08)
|
* [Draft: TMCH Functional Spec]
|
||||||
* [Draft: Mark and Signed Mark Objects Mapping](https://tools.ietf.org/html/draft-lozano-tmch-smd-02)
|
(http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08)
|
||||||
|
* [Draft: Mark and Signed Mark Objects Mapping]
|
||||||
|
(https://tools.ietf.org/html/draft-lozano-tmch-smd-02)
|
||||||
|
|
||||||
### WHOIS
|
### WHOIS
|
||||||
|
|
||||||
|
@ -134,8 +140,10 @@ internal HTTP endpoint running on `/_dr/whois`. A separate proxy running on port
|
||||||
43 forwards requests to that path. Domain Registry also implements a public HTTP
|
43 forwards requests to that path. Domain Registry also implements a public HTTP
|
||||||
endpoint that listens on the `/whois` path.
|
endpoint that listens on the `/whois` path.
|
||||||
|
|
||||||
* [RFC 3912: WHOIS Protocol Specification](https://tools.ietf.org/html/rfc3912)
|
* [RFC 3912: WHOIS Protocol Specification]
|
||||||
* [RFC 7485: Inventory and Analysis of Registration Objects](http://tools.ietf.org/html/rfc7485)
|
(https://tools.ietf.org/html/rfc3912)
|
||||||
|
* [RFC 7485: Inventory and Analysis of Registration Objects]
|
||||||
|
(http://tools.ietf.org/html/rfc7485)
|
||||||
|
|
||||||
### Registration Data Access Protocol (RDAP)
|
### Registration Data Access Protocol (RDAP)
|
||||||
|
|
||||||
|
@ -143,23 +151,24 @@ RDAP is the new standard for WHOIS. It provides much richer functionality, such
|
||||||
as the ability to perform wildcard searches. Domain Registry makes this HTTP
|
as the ability to perform wildcard searches. Domain Registry makes this HTTP
|
||||||
service available under the `/rdap/...` path.
|
service available under the `/rdap/...` path.
|
||||||
|
|
||||||
* [RFC 7480: RDAP HTTP Usage](http://tools.ietf.org/html/rfc7480)
|
* [RFC 7480: RDAP HTTP Usage](http://tools.ietf.org/html/rfc7480)
|
||||||
* [RFC 7481: RDAP Security Services](http://tools.ietf.org/html/rfc7481)
|
* [RFC 7481: RDAP Security Services](http://tools.ietf.org/html/rfc7481)
|
||||||
* [RFC 7482: RDAP Query Format](http://tools.ietf.org/html/rfc7482)
|
* [RFC 7482: RDAP Query Format](http://tools.ietf.org/html/rfc7482)
|
||||||
* [RFC 7483: RDAP JSON Responses](http://tools.ietf.org/html/rfc7483)
|
* [RFC 7483: RDAP JSON Responses](http://tools.ietf.org/html/rfc7483)
|
||||||
* [RFC 7484: RDAP Finding the Authoritative Registration Data](http://tools.ietf.org/html/rfc7484)
|
* [RFC 7484: RDAP Finding the Authoritative Registration Data]
|
||||||
|
(http://tools.ietf.org/html/rfc7484)
|
||||||
|
|
||||||
### Backups
|
### Backups
|
||||||
|
|
||||||
The registry provides a system for generating and restoring from backups with
|
The registry provides a system for generating and restoring from backups with
|
||||||
strong point-in-time consistency. Datastore backups are written out once daily
|
strong point-in-time consistency. Datastore backups are written out once daily
|
||||||
to Cloud Storage using the built-in Datastore snapshot export functionality.
|
to Cloud Storage using the built-in Datastore snapshot export functionality.
|
||||||
Separately, entities called commit logs are continuously exported to track
|
Separately, entities called commit logs are continuously exported to track
|
||||||
changes that occur in between the regularly scheduled backups.
|
changes that occur in between the regularly scheduled backups.
|
||||||
|
|
||||||
A restore involves wiping out all entities in Datastore, importing the most
|
A restore involves wiping out all entities in Datastore, importing the most
|
||||||
recent complete daily backup snapshot, then replaying all of the commit logs
|
recent complete daily backup snapshot, then replaying all of the commit logs
|
||||||
since that snapshot. This yields a system state that is guaranteed
|
since that snapshot. This yields a system state that is guaranteed
|
||||||
transactionally consistent.
|
transactionally consistent.
|
||||||
|
|
||||||
### Billing
|
### Billing
|
||||||
|
@ -173,24 +182,26 @@ monthly invoices per registrar.
|
||||||
|
|
||||||
Because the registry runs on the Google Cloud Platform stack, it benefits from
|
Because the registry runs on the Google Cloud Platform stack, it benefits from
|
||||||
high availability, automatic fail-over, and horizontal auto-scaling of compute
|
high availability, automatic fail-over, and horizontal auto-scaling of compute
|
||||||
and database resources. This makes it quite flexible for running TLDs of any
|
and database resources. This makes it quite flexible for running TLDs of any
|
||||||
size.
|
size.
|
||||||
|
|
||||||
### Automated tests
|
### Automated tests
|
||||||
|
|
||||||
The registry codebase includes ~400 test classes with ~4,000 total unit and
|
The registry codebase includes ~400 test classes with ~4,000 total unit and
|
||||||
integration tests. This limits regressions, ensures correct system
|
integration tests. This limits regressions, ensures correct system
|
||||||
functionality, and allows for easy continued future development and refactoring.
|
functionality, and allows for easy continued future development and refactoring.
|
||||||
|
|
||||||
### DNS
|
### DNS
|
||||||
|
|
||||||
An interface for DNS operations is provided, along with a sample implementation
|
An interface for DNS operations is provided, along with a sample implementation
|
||||||
that uses the [Google Cloud DNS](https://cloud.google.com/dns/) API. A bulk
|
that uses the [Google Cloud DNS](https://cloud.google.com/dns/) API. A bulk
|
||||||
export tool is also provided to export a zone file for an entire TLD in BIND
|
export tool is also provided to export a zone file for an entire TLD in BIND
|
||||||
format.
|
format.
|
||||||
|
|
||||||
* [RFC 1034: Domain Names - Concepts and Facilities](https://www.ietf.org/rfc/rfc1034.txt)
|
* [RFC 1034: Domain Names - Concepts and Facilities]
|
||||||
* [RFC 1035: Domain Names - Implementation and Specification](https://www.ietf.org/rfc/rfc1034.txt)
|
(https://www.ietf.org/rfc/rfc1034.txt)
|
||||||
|
* [RFC 1035: Domain Names - Implementation and Specification]
|
||||||
|
(https://www.ietf.org/rfc/rfc1034.txt)
|
||||||
|
|
||||||
### Exports
|
### Exports
|
||||||
|
|
||||||
|
@ -202,21 +213,20 @@ ICANN-mandated reports, database snapshots, and reserved terms.
|
||||||
### Metrics and reporting
|
### Metrics and reporting
|
||||||
|
|
||||||
The registry records metrics and regularly exports them to BigQuery so that
|
The registry records metrics and regularly exports them to BigQuery so that
|
||||||
analyses can be run on them using full SQL queries. Metrics include which EPP
|
analyses can be run on them using full SQL queries. Metrics include which EPP
|
||||||
commands were run and when and by whom, information on failed commands, activity
|
commands were run and when and by whom, information on failed commands, activity
|
||||||
per registrar, and length of each request.
|
per registrar, and length of each request.
|
||||||
|
|
||||||
[BigQuery][bigquery] reporting scripts are provided to generate the required
|
[BigQuery][bigquery] reporting scripts are provided to generate the required
|
||||||
per-TLD monthly
|
per-TLD monthly [registry reports]
|
||||||
[registry reports](https://www.icann.org/resources/pages/registry-reports) for
|
(https://www.icann.org/resources/pages/registry-reports) for ICANN.
|
||||||
ICANN.
|
|
||||||
|
|
||||||
### Registrar console
|
### Registrar console
|
||||||
|
|
||||||
The registry includes a web-based registrar console that registrars can access
|
The registry includes a web-based registrar console that registrars can access
|
||||||
in a browser. It provides the ability for registrars to view their billing
|
in a browser. It provides the ability for registrars to view their billing
|
||||||
invoices in Google Drive, contact the registry provider, and modify WHOIS,
|
invoices in Google Drive, contact the registry provider, and modify WHOIS,
|
||||||
security (including SSL certificates), and registrar contact settings. Main
|
security (including SSL certificates), and registrar contact settings. Main
|
||||||
registry commands such as creating domains, hosts, and contacts must go through
|
registry commands such as creating domains, hosts, and contacts must go through
|
||||||
EPP and are not provided in the console.
|
EPP and are not provided in the console.
|
||||||
|
|
||||||
|
@ -231,7 +241,7 @@ system, and creating new TLDs.
|
||||||
### Plug-and-play pricing engines
|
### Plug-and-play pricing engines
|
||||||
|
|
||||||
The registry has the ability to configure per-TLD pricing engines to
|
The registry has the ability to configure per-TLD pricing engines to
|
||||||
programmatically determine the price of domain names on the fly. An
|
programmatically determine the price of domain names on the fly. An
|
||||||
implementation is provided that uses the contents of a static list of prices
|
implementation is provided that uses the contents of a static list of prices
|
||||||
(this being by far the most common type of premium pricing used for TLDs).
|
(this being by far the most common type of premium pricing used for TLDs).
|
||||||
|
|
||||||
|
@ -240,23 +250,23 @@ implementation is provided that uses the contents of a static list of prices
|
||||||
There are a few things that the registry cannot currently do, and a few things
|
There are a few things that the registry cannot currently do, and a few things
|
||||||
that are out of scope that it will never do.
|
that are out of scope that it will never do.
|
||||||
|
|
||||||
* You will need a DNS system in order to run a fully-fledged registry. If you
|
* You will need a DNS system in order to run a fully-fledged registry. If you
|
||||||
are planning on using anything other than Google Cloud DNS you will need to
|
are planning on using anything other than Google Cloud DNS you will need to
|
||||||
provide an implementation.
|
provide an implementation.
|
||||||
* You will need an invoicing system to convert the internal registry billing
|
* You will need an invoicing system to convert the internal registry billing
|
||||||
events into registrar invoices using whatever accounts receivable setup you
|
events into registrar invoices using whatever accounts receivable setup you
|
||||||
already have. A partial implementation is provided that generates generic CSV
|
already have. A partial implementation is provided that generates generic
|
||||||
invoices (see `MakeBillingTablesCommand`), but you will need to integrate it
|
CSV invoices (see `MakeBillingTablesCommand`), but you will need to
|
||||||
with your payments system.
|
integrate it with your payments system.
|
||||||
* You will likely need monitoring to continuously monitor the status of the
|
* You will likely need monitoring to continuously monitor the status of the
|
||||||
system. Any of a large variety of tools can be used for this, or you can
|
system. Any of a large variety of tools can be used for this, or you can
|
||||||
write your own.
|
write your own.
|
||||||
* You will need a proxy to forward traffic on EPP and WHOIS ports to the HTTPS
|
* You will need a proxy to forward traffic on EPP and WHOIS ports to the HTTPS
|
||||||
endpoint on App Engine, as App Engine only allows incoming traffic on
|
endpoint on App Engine, as App Engine only allows incoming traffic on
|
||||||
HTTP/HTTPS ports. Similarly, App Engine does not yet support IPv6, so your
|
HTTP/HTTPS ports. Similarly, App Engine does not yet support IPv6, so your
|
||||||
proxy would have to support that as well if you need IPv6 support. Future
|
proxy would have to support that as well if you need IPv6 support. Future
|
||||||
versions of [App Engine Flexible][flex] should provide these out of the box,
|
versions of [App Engine Flexible][flex] should provide these out of the box,
|
||||||
but they aren't ready yet.
|
but they aren't ready yet.
|
||||||
|
|
||||||
[bigquery]: https://cloud.google.com/bigquery/
|
[bigquery]: https://cloud.google.com/bigquery/
|
||||||
[datastore]: https://cloud.google.com/datastore/docs/concepts/overview
|
[datastore]: https://cloud.google.com/datastore/docs/concepts/overview
|
||||||
|
|
|
@ -5,19 +5,19 @@ Registry project as it is implemented in App Engine.
|
||||||
|
|
||||||
## Services
|
## Services
|
||||||
|
|
||||||
The Domain Registry contains three
|
The Domain Registry contains three [services]
|
||||||
[services](https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine),
|
(https://cloud.google.com/appengine/docs/python/an-overview-of-app-engine),
|
||||||
which were previously called modules in earlier versions of App Engine. The
|
which were previously called modules in earlier versions of App Engine. The
|
||||||
services are: default (also called front-end), backend, and tools. Each service
|
services are: default (also called front-end), backend, and tools. Each service
|
||||||
runs independently in a lot of ways, including that they can be upgraded
|
runs independently in a lot of ways, including that they can be upgraded
|
||||||
individually, their log outputs are separate, and their servers and configured
|
individually, their log outputs are separate, and their servers and configured
|
||||||
scaling are separate as well.
|
scaling are separate as well.
|
||||||
|
|
||||||
Once you have your app deployed and running, the default service can be accessed
|
Once you have your app deployed and running, the default service can be accessed
|
||||||
at `https://project-id.appspot.com`, substituting whatever your App Engine app
|
at `https://project-id.appspot.com`, substituting whatever your App Engine app
|
||||||
is named for "project-id". Note that that is the URL for the production
|
is named for "project-id". Note that that is the URL for the production instance
|
||||||
instance of your app; other environments will have the environment name appended
|
of your app; other environments will have the environment name appended with a
|
||||||
with a hyphen in the hostname, e.g. `https://project-id-sandbox.appspot.com`.
|
hyphen in the hostname, e.g. `https://project-id-sandbox.appspot.com`.
|
||||||
|
|
||||||
The URL for the backend service is `https://backend-dot-project-id.appspot.com`
|
The URL for the backend service is `https://backend-dot-project-id.appspot.com`
|
||||||
and the URL for the tools service is `https://tools-dot-project-id.appspot.com`.
|
and the URL for the tools service is `https://tools-dot-project-id.appspot.com`.
|
||||||
|
@ -27,32 +27,32 @@ wild-cards).
|
||||||
|
|
||||||
### Default service
|
### Default service
|
||||||
|
|
||||||
The default service is responsible for all registrar-facing
|
The default service is responsible for all registrar-facing [EPP]
|
||||||
[EPP](https://en.wikipedia.org/wiki/Extensible_Provisioning_Protocol) command
|
(https://en.wikipedia.org/wiki/Extensible_Provisioning_Protocol) command
|
||||||
traffic, all user-facing WHOIS and RDAP traffic, and the admin and registrar web
|
traffic, all user-facing WHOIS and RDAP traffic, and the admin and registrar web
|
||||||
consoles, and is thus the most important service. If the service has any
|
consoles, and is thus the most important service. If the service has any
|
||||||
problems and goes down or stops servicing requests in a timely manner, it will
|
problems and goes down or stops servicing requests in a timely manner, it will
|
||||||
begin to impact users immediately. Requests to the default service are handled
|
begin to impact users immediately. Requests to the default service are handled
|
||||||
by the `FrontendServlet`, which provides all of the endpoints exposed in
|
by the `FrontendServlet`, which provides all of the endpoints exposed in
|
||||||
`FrontendRequestComponent`.
|
`FrontendRequestComponent`.
|
||||||
|
|
||||||
### Backend service
|
### Backend service
|
||||||
|
|
||||||
The backend service is responsible for executing all regularly scheduled
|
The backend service is responsible for executing all regularly scheduled
|
||||||
background tasks (using cron) as well as all asynchronous tasks. Requests to
|
background tasks (using cron) as well as all asynchronous tasks. Requests to the
|
||||||
the backend service are handled by the `BackendServlet`, which provides all of
|
backend service are handled by the `BackendServlet`, which provides all of the
|
||||||
the endpoints exposed in `BackendRequestComponent`. These include tasks for
|
endpoints exposed in `BackendRequestComponent`. These include tasks for
|
||||||
generating/exporting RDE, syncing the trademark list from TMDB, exporting
|
generating/exporting RDE, syncing the trademark list from TMDB, exporting
|
||||||
backups, writing out DNS updates, handling asynchronous contact and host
|
backups, writing out DNS updates, handling asynchronous contact and host
|
||||||
deletions, writing out commit logs, exporting metrics to BigQuery, and many
|
deletions, writing out commit logs, exporting metrics to BigQuery, and many
|
||||||
more. Issues in the backend service will not immediately be apparent to end
|
more. Issues in the backend service will not immediately be apparent to end
|
||||||
users, but the longer it is down, the more obvious it will become that
|
users, but the longer it is down, the more obvious it will become that
|
||||||
user-visible tasks such as DNS and deletion are not being handled in a timely
|
user-visible tasks such as DNS and deletion are not being handled in a timely
|
||||||
manner.
|
manner.
|
||||||
|
|
||||||
The backend service is also where all MapReduces run, which includes some of the
|
The backend service is also where all MapReduces run, which includes some of the
|
||||||
aforementioned tasks such as RDE and asynchronous resource deletion, as well as
|
aforementioned tasks such as RDE and asynchronous resource deletion, as well as
|
||||||
any one-off data migration MapReduces. Consequently, the backend service should
|
any one-off data migration MapReduces. Consequently, the backend service should
|
||||||
be sized to support not just the normal ongoing DNS load but also the load
|
be sized to support not just the normal ongoing DNS load but also the load
|
||||||
incurred by MapReduces, both scheduled (such as RDE) and on-demand (asynchronous
|
incurred by MapReduces, both scheduled (such as RDE) and on-demand (asynchronous
|
||||||
contact/host deletion).
|
contact/host deletion).
|
||||||
|
@ -61,364 +61,369 @@ contact/host deletion).
|
||||||
|
|
||||||
The tools service is responsible for servicing requests from the `registry_tool`
|
The tools service is responsible for servicing requests from the `registry_tool`
|
||||||
command line tool, which provides administrative-level functionality for
|
command line tool, which provides administrative-level functionality for
|
||||||
developers and tech support employees of the registry. It is thus the least
|
developers and tech support employees of the registry. It is thus the least
|
||||||
critical of the three services. Requests to the tools service are handled by
|
critical of the three services. Requests to the tools service are handled by the
|
||||||
the `ToolsServlet`, which provides all of the endpoints exposed in
|
`ToolsServlet`, which provides all of the endpoints exposed in
|
||||||
`ToolsRequestComponent`. Some example functionality that this service provides
|
`ToolsRequestComponent`. Some example functionality that this service provides
|
||||||
includes the server-side code to update premium lists, run EPP commands from the
|
includes the server-side code to update premium lists, run EPP commands from the
|
||||||
tool, and manually modify contacts/hosts/domains/and other resources. Problems
|
tool, and manually modify contacts/hosts/domains/and other resources. Problems
|
||||||
with the tools service are not visible to users.
|
with the tools service are not visible to users.
|
||||||
|
|
||||||
## Task queues
|
## Task queues
|
||||||
|
|
||||||
[Task queues](https://cloud.google.com/appengine/docs/java/taskqueue/) in App
|
[Task queues](https://cloud.google.com/appengine/docs/java/taskqueue/) in App
|
||||||
Engine provide an asynchronous way to enqueue tasks and then execute them on
|
Engine provide an asynchronous way to enqueue tasks and then execute them on
|
||||||
some kind of schedule. There are two types of queues, push queues and pull
|
some kind of schedule. There are two types of queues, push queues and pull
|
||||||
queues. Tasks in push queues are always executing up to some throttlable limit.
|
queues. Tasks in push queues are always executing up to some throttlable limit.
|
||||||
Tasks in pull queues remain there indefinitely until the queue is polled by code
|
Tasks in pull queues remain there indefinitely until the queue is polled by code
|
||||||
that is running for some other reason. Essentially, push queues run their own
|
that is running for some other reason. Essentially, push queues run their own
|
||||||
tasks while pull queues just enqueue data that is used by something else. Many
|
tasks while pull queues just enqueue data that is used by something else. Many
|
||||||
other parts of App Engine are implemented using task queues. For example,
|
other parts of App Engine are implemented using task queues. For example, [App
|
||||||
[App Engine cron](https://cloud.google.com/appengine/docs/java/config/cron) adds
|
Engine cron](https://cloud.google.com/appengine/docs/java/config/cron) adds
|
||||||
tasks to push queues at regularly scheduled intervals, and the
|
tasks to push queues at regularly scheduled intervals, and the [MapReduce
|
||||||
[MapReduce framework](https://cloud.google.com/appengine/docs/java/dataprocessing/)
|
framework](https://cloud.google.com/appengine/docs/java/dataprocessing/) adds
|
||||||
adds tasks for each phase of the MapReduce algorithm.
|
tasks for each phase of the MapReduce algorithm.
|
||||||
|
|
||||||
The Domain Registry project uses a particular pattern of paired push/pull queues
|
The Domain Registry project uses a particular pattern of paired push/pull queues
|
||||||
that is worth explaining in detail. Push queues are essential because App
|
that is worth explaining in detail. Push queues are essential because App
|
||||||
Engine's architecture does not support long-running background processes, and so
|
Engine's architecture does not support long-running background processes, and so
|
||||||
push queues are thus the fundamental building block that allows asynchronous and
|
push queues are thus the fundamental building block that allows asynchronous and
|
||||||
background execution of code that is not in response to incoming web requests.
|
background execution of code that is not in response to incoming web requests.
|
||||||
However, they also have limitations in that they do not allow batch processing
|
However, they also have limitations in that they do not allow batch processing
|
||||||
or grouping. That's where the pull queue comes in. Regularly scheduled tasks
|
or grouping. That's where the pull queue comes in. Regularly scheduled tasks in
|
||||||
in the push queue will, upon execution, poll the corresponding pull queue for a
|
the push queue will, upon execution, poll the corresponding pull queue for a
|
||||||
specified number of tasks and execute them in a batch. This allows the code to
|
specified number of tasks and execute them in a batch. This allows the code to
|
||||||
execute in the background while taking advantage of batch processing.
|
execute in the background while taking advantage of batch processing.
|
||||||
|
|
||||||
Particulars on the task queues in use by the Domain Registry project are
|
Particulars on the task queues in use by the Domain Registry project are
|
||||||
specified in the `queue.xml` file. Note that many push queues have a direct
|
specified in the `queue.xml` file. Note that many push queues have a direct
|
||||||
one-to-one correspondence with entries in `cron.xml` because they need to be
|
one-to-one correspondence with entries in `cron.xml` because they need to be
|
||||||
fanned-out on a per-TLD or other basis (see the Cron section below for more
|
fanned-out on a per-TLD or other basis (see the Cron section below for more
|
||||||
explanation). The exact queue that a given cron task will use is passed as the
|
explanation). The exact queue that a given cron task will use is passed as the
|
||||||
query string parameter "queue" in the url specification for the cron task.
|
query string parameter "queue" in the url specification for the cron task.
|
||||||
|
|
||||||
Here are the task queues in use by the system. All are push queues unless
|
Here are the task queues in use by the system. All are push queues unless
|
||||||
explicitly marked as otherwise.
|
explicitly marked as otherwise.
|
||||||
|
|
||||||
* `bigquery-streaming-metrics` -- Queue for metrics that are asynchronously
|
* `bigquery-streaming-metrics` -- Queue for metrics that are asynchronously
|
||||||
streamed to BigQuery in the `Metrics` class. Tasks are enqueued during EPP
|
streamed to BigQuery in the `Metrics` class. Tasks are enqueued during EPP
|
||||||
flows in `EppController`. This means that there is a lag of a few seconds to
|
flows in `EppController`. This means that there is a lag of a few seconds to
|
||||||
a few minutes between when metrics are generated and when they are queryable
|
a few minutes between when metrics are generated and when they are queryable
|
||||||
in BigQuery, but this is preferable to slowing all EPP flows down and blocking
|
in BigQuery, but this is preferable to slowing all EPP flows down and
|
||||||
them on BigQuery streaming.
|
blocking them on BigQuery streaming.
|
||||||
* `brda` -- Queue for tasks to upload weekly Bulk Registration Data Access
|
* `brda` -- Queue for tasks to upload weekly Bulk Registration Data Access
|
||||||
(BRDA) files to a location where they are available to ICANN. The
|
(BRDA) files to a location where they are available to ICANN. The
|
||||||
`RdeStagingReducer` (part of the RDE MapReduce) creates these tasks at the end
|
`RdeStagingReducer` (part of the RDE MapReduce) creates these tasks at the
|
||||||
of generating an RDE dump.
|
end of generating an RDE dump.
|
||||||
* `delete-commits` -- Cron queue for tasks to regularly delete commit logs that
|
* `delete-commits` -- Cron queue for tasks to regularly delete commit logs
|
||||||
are more than thirty days stale. These tasks execute the
|
that are more than thirty days stale. These tasks execute the
|
||||||
`DeleteOldCommitLogsAction`.
|
`DeleteOldCommitLogsAction`.
|
||||||
* `dns-cron` (cron queue) and `dns-pull` (pull queue) -- A push/pull pair of
|
* `dns-pull` -- A pull queue to enqueue DNS modifications. Cron regularly runs
|
||||||
queues. Cron regularly enqueues tasks in dns-cron each minute, which are then
|
`ReadDnsQueueAction`, which drains the queue, batches modifications by TLD,
|
||||||
executed by `ReadDnsQueueAction`, which leases a batch of tasks from the pull
|
and writes the batches to `dns-publish` to be published to the configured
|
||||||
queue, groups them by TLD, and writes them as a single task to `dns-publish`
|
`DnsWriter` for the TLD.
|
||||||
to be published to the configured DNS writer for the TLD.
|
* `dns-publish` -- Queue for batches of DNS updates to be pushed to DNS
|
||||||
* `dns-publish` -- Queue for batches of DNS updates to be pushed to DNS writers.
|
writers.
|
||||||
* `export-bigquery-poll` -- Queue for tasks to query the success/failure of a
|
* `export-bigquery-poll` -- Queue for tasks to query the success/failure of a
|
||||||
given BigQuery export job. Tasks are enqueued by `BigqueryPollJobAction`.
|
given BigQuery export job. Tasks are enqueued by `BigqueryPollJobAction`.
|
||||||
* `export-commits` -- Queue for tasks to export commit log checkpoints. Tasks
|
* `export-commits` -- Queue for tasks to export commit log checkpoints. Tasks
|
||||||
are enqueued by `CommitLogCheckpointAction` (which is run every minute by
|
are enqueued by `CommitLogCheckpointAction` (which is run every minute by
|
||||||
cron) and executed by `ExportCommitLogDiffAction`.
|
cron) and executed by `ExportCommitLogDiffAction`.
|
||||||
* `export-reserved-terms` -- Cron queue for tasks to export the list of reserved
|
* `export-reserved-terms` -- Cron queue for tasks to export the list of
|
||||||
terms for each TLD. The tasks are executed by `ExportReservedTermsAction`.
|
reserved terms for each TLD. The tasks are executed by
|
||||||
* `export-snapshot` -- Cron and push queue for tasks to load a Datastore
|
`ExportReservedTermsAction`.
|
||||||
snapshot that was stored in Google Cloud Storage and export it to BigQuery.
|
* `export-snapshot` -- Cron and push queue for tasks to load a Datastore
|
||||||
Tasks are enqueued by both cron and `CheckSnapshotServlet` and are executed by
|
snapshot that was stored in Google Cloud Storage and export it to BigQuery.
|
||||||
both `ExportSnapshotServlet` and `LoadSnapshotAction`.
|
Tasks are enqueued by both cron and `CheckSnapshotServlet` and are executed
|
||||||
* `export-snapshot-poll` -- Queue for tasks to check that a Datastore snapshot
|
by both `ExportSnapshotServlet` and `LoadSnapshotAction`.
|
||||||
has been successfully uploaded to Google Cloud Storage (this is an
|
* `export-snapshot-poll` -- Queue for tasks to check that a Datastore snapshot
|
||||||
asynchronous background operation that can take an indeterminate amount of
|
has been successfully uploaded to Google Cloud Storage (this is an
|
||||||
time). Once the snapshot is successfully uploaded, it is imported into
|
asynchronous background operation that can take an indeterminate amount of
|
||||||
BigQuery. Tasks are enqueued by `ExportSnapshotServlet` and executed by
|
time). Once the snapshot is successfully uploaded, it is imported into
|
||||||
`CheckSnapshotServlet`.
|
BigQuery. Tasks are enqueued by `ExportSnapshotServlet` and executed by
|
||||||
* `export-snapshot-update-view` -- Queue for tasks to update the BigQuery views
|
`CheckSnapshotServlet`.
|
||||||
to point to the most recently uploaded snapshot. Tasks are enqueued by
|
* `export-snapshot-update-view` -- Queue for tasks to update the BigQuery
|
||||||
`LoadSnapshotAction` and executed by `UpdateSnapshotViewAction`.
|
views to point to the most recently uploaded snapshot. Tasks are enqueued by
|
||||||
* `flows-async` -- Queue for asynchronous tasks that are enqueued during EPP
|
`LoadSnapshotAction` and executed by `UpdateSnapshotViewAction`.
|
||||||
command flows. Currently all of these tasks correspond to invocations of any
|
* `flows-async` -- Queue for asynchronous tasks that are enqueued during EPP
|
||||||
of the following three MapReduces: `DnsRefreshForHostRenameAction`,
|
command flows. Currently all of these tasks correspond to invocations of any
|
||||||
`DeleteHostResourceAction`, or `DeleteContactResourceAction`.
|
of the following three MapReduces: `DnsRefreshForHostRenameAction`,
|
||||||
* `group-members-sync` -- Cron queue for tasks to sync registrar contacts (not
|
`DeleteHostResourceAction`, or `DeleteContactResourceAction`.
|
||||||
domain contacts!) to Google Groups. Tasks are executed by
|
* `group-members-sync` -- Cron queue for tasks to sync registrar contacts (not
|
||||||
`SyncGroupMembersAction`.
|
domain contacts!) to Google Groups. Tasks are executed by
|
||||||
* `load[0-9]` -- Queues used to load-test the system by `LoadTestAction`. These
|
`SyncGroupMembersAction`.
|
||||||
queues don't need to exist except when actively running load tests (which is
|
* `load[0-9]` -- Queues used to load-test the system by `LoadTestAction`.
|
||||||
not recommended on production environments). There are ten of these queues to
|
These queues don't need to exist except when actively running load tests
|
||||||
provide simple sharding, because the Domain Registry system is capable of
|
(which is not recommended on production environments). There are ten of
|
||||||
handling significantly more Queries Per Second than the highest throttle limit
|
these queues to provide simple sharding, because the Domain Registry system
|
||||||
available on task queues (which is 500 qps).
|
is capable of handling significantly more Queries Per Second than the
|
||||||
* `lordn-claims` and `lordn-sunrise` -- Pull queues for handling LORDN exports.
|
highest throttle limit available on task queues (which is 500 qps).
|
||||||
Tasks are enqueued synchronously during EPP commands depending on whether the
|
* `lordn-claims` and `lordn-sunrise` -- Pull queues for handling LORDN
|
||||||
domain name in question has a claims notice ID.
|
exports. Tasks are enqueued synchronously during EPP commands depending on
|
||||||
* `marksdb` -- Queue for tasks to verify that an upload to NORDN was
|
whether the domain name in question has a claims notice ID.
|
||||||
successfully received and verified. These tasks are enqueued by
|
* `marksdb` -- Queue for tasks to verify that an upload to NORDN was
|
||||||
`NordnUploadAction` following an upload and are executed by
|
successfully received and verified. These tasks are enqueued by
|
||||||
`NordnVerifyAction`.
|
`NordnUploadAction` following an upload and are executed by
|
||||||
* `nordn` -- Cron queue used for NORDN exporting. Tasks are executed by
|
`NordnVerifyAction`.
|
||||||
`NordnUploadAction`, which pulls LORDN data from the `lordn-claims` and
|
* `nordn` -- Cron queue used for NORDN exporting. Tasks are executed by
|
||||||
`lordn-sunrise` pull queues (above).
|
`NordnUploadAction`, which pulls LORDN data from the `lordn-claims` and
|
||||||
* `rde-report` -- Queue for tasks to upload RDE reports to ICANN following
|
`lordn-sunrise` pull queues (above).
|
||||||
successful upload of full RDE files to the escrow provider. Tasks are
|
* `rde-report` -- Queue for tasks to upload RDE reports to ICANN following
|
||||||
enqueued by `RdeUploadAction` and executed by `RdeReportAction`.
|
successful upload of full RDE files to the escrow provider. Tasks are
|
||||||
* `rde-upload` -- Cron queue for tasks to upload already-generated RDE files
|
enqueued by `RdeUploadAction` and executed by `RdeReportAction`.
|
||||||
from Cloud Storage to the escrow provider. Tasks are executed by
|
* `rde-upload` -- Cron queue for tasks to upload already-generated RDE files
|
||||||
`RdeUploadAction`.
|
from Cloud Storage to the escrow provider. Tasks are executed by
|
||||||
* `sheet` -- Queue for tasks to sync registrar updates to a Google Sheets
|
`RdeUploadAction`.
|
||||||
spreadsheet. Tasks are enqueued by `RegistrarServlet` when changes are made
|
* `sheet` -- Queue for tasks to sync registrar updates to a Google Sheets
|
||||||
to registrar fields and are executed by `SyncRegistrarsSheetAction`.
|
spreadsheet. Tasks are enqueued by `RegistrarServlet` when changes are made
|
||||||
|
to registrar fields and are executed by `SyncRegistrarsSheetAction`.
|
||||||
|
|
||||||
## Environments
|
## Environments
|
||||||
|
|
||||||
The domain registry codebase comes pre-configured with support for a number of
|
The domain registry codebase comes pre-configured with support for a number of
|
||||||
different environments, all of which are used in Google's registry system.
|
different environments, all of which are used in Google's registry system. Other
|
||||||
Other registry operators may choose to user more or fewer environments,
|
registry operators may choose to user more or fewer environments, depending on
|
||||||
depending on their needs.
|
their needs.
|
||||||
|
|
||||||
The different environments are specified in `RegistryEnvironment`. Most
|
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 App Engine app except for `UNITTEST` and `LOCAL`, which
|
||||||
by their nature do not use real environments running in the cloud. The
|
by their nature do not use real environments running in the cloud. The
|
||||||
recommended naming scheme for the App Engine apps that has the best possible
|
recommended naming scheme for the App Engine apps that has the best possible
|
||||||
compatibility with the codebase and thus requires the least configuration is to
|
compatibility with the codebase and thus requires the least configuration is to
|
||||||
pick a name for the production app and then suffix it for the other
|
pick a name for the production app and then suffix it for the other
|
||||||
environments. E.g., if the production app is to be named 'registry-platform',
|
environments. E.g., if the production app is to be named 'registry-platform',
|
||||||
then the sandbox app would be named 'registry-platform-sandbox'.
|
then the sandbox app would be named 'registry-platform-sandbox'.
|
||||||
|
|
||||||
The full list of environments supported out-of-the-box, in descending order from
|
The full list of environments supported out-of-the-box, in descending order from
|
||||||
real to not, is:
|
real to not, is:
|
||||||
|
|
||||||
* `PRODUCTION` -- The real production environment that is actually running live
|
* `PRODUCTION` -- The real production environment that is actually running
|
||||||
TLDs. Since the Domain Registry is a shared registry platform, there need
|
live TLDs. Since the Domain Registry is a shared registry platform, there
|
||||||
only ever be one of these.
|
need only ever be one of these.
|
||||||
* `SANDBOX` -- A playground environment for external users to test commands in
|
* `SANDBOX` -- A playground environment for external users to test commands in
|
||||||
without the possibility of affecting production data. This is the environment
|
without the possibility of affecting production data. This is the
|
||||||
new registrars go through
|
environment new registrars go through [OT&E]
|
||||||
[OT&E](https://www.icann.org/resources/unthemed-pages/registry-agmt-appc-e-2001-04-26-en)
|
(https://www.icann.org/resources/unthemed-pages/registry-agmt-appc-e-2001-04-26-en)
|
||||||
in. Sandbox is also useful as a final sanity check to push a new prospective
|
in. Sandbox is also useful as a final sanity check to push a new prospective
|
||||||
build to and allow it to "bake" before pushing it to production.
|
build to and allow it to "bake" before pushing it to production.
|
||||||
* `QA` -- An internal environment used by business users to play with and sign
|
* `QA` -- An internal environment used by business users to play with and sign
|
||||||
off on new features to be released. This environment can be pushed to
|
off on new features to be released. This environment can be pushed to
|
||||||
frequently and is where manual testers should be spending the majority of
|
frequently and is where manual testers should be spending the majority of
|
||||||
their time.
|
their time.
|
||||||
* `CRASH` -- Another environment similar to QA, except with no expectations of
|
* `CRASH` -- Another environment similar to QA, except with no expectations of
|
||||||
data preservation. Crash is used for testing of backup/restore (which brings
|
data preservation. Crash is used for testing of backup/restore (which brings
|
||||||
the entire system down until it is completed) without affecting the QA
|
the entire system down until it is completed) without affecting the QA
|
||||||
environment.
|
environment.
|
||||||
* `ALPHA` -- The developers' playground. Experimental builds are routinely
|
* `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 app running on App Engine. You
|
||||||
may end up wanting multiple environments like Alpha if you regularly
|
may end up wanting multiple environments like Alpha if you regularly
|
||||||
experience contention (i.e. developers being blocked from testing their code
|
experience contention (i.e. developers being blocked from testing their code
|
||||||
on Alpha because others are already using it).
|
on Alpha because others are already using it).
|
||||||
* `LOCAL` -- A fake environment that is used when running the app locally on a
|
* `LOCAL` -- A fake environment that is used when running the app locally on a
|
||||||
simulated App Engine instance.
|
simulated App Engine instance.
|
||||||
* `UNITTEST` -- A fake environment that is used in unit tests, where everything
|
* `UNITTEST` -- A fake environment that is used in unit tests, where
|
||||||
in the App Engine stack is simulated or mocked.
|
everything in the App Engine stack is simulated or mocked.
|
||||||
|
|
||||||
## Release process
|
## Release process
|
||||||
|
|
||||||
The following is a recommended release process based on Google's several years
|
The following is a recommended release process based on Google's several years
|
||||||
of experience running a production registry using this codebase.
|
of experience running a production registry using this codebase.
|
||||||
|
|
||||||
1. Developers write code and associated unit tests verifying that the new code
|
1. Developers write code and associated unit tests verifying that the new code
|
||||||
works properly.
|
works properly.
|
||||||
2. New features or potentially risky bug fixes are pushed to Alpha and tested by
|
2. New features or potentially risky bug fixes are pushed to Alpha and tested
|
||||||
the developers before being committed to the source code repository.
|
by the developers before being committed to the source code repository.
|
||||||
3. New builds are cut and first pushed to Sandbox.
|
3. New builds are cut and first pushed to Sandbox.
|
||||||
4. Once a build has been running successfully in Sandbox for a day with no
|
4. Once a build has been running successfully in Sandbox for a day with no
|
||||||
errors, it can be pushed to Production.
|
errors, it can be pushed to Production.
|
||||||
5. Repeat once weekly, or potentially more often.
|
5. Repeat once weekly, or potentially more often.
|
||||||
|
|
||||||
## Cron tasks
|
## Cron tasks
|
||||||
|
|
||||||
All [cron tasks](https://cloud.google.com/appengine/docs/java/config/cron) are
|
All [cron tasks](https://cloud.google.com/appengine/docs/java/config/cron) are
|
||||||
specified in `cron.xml` files, with one per environment. There are more tasks
|
specified in `cron.xml` files, with one per environment. There are more tasks
|
||||||
that execute in Production than in other environments, because tasks like
|
that execute in Production than in other environments, because tasks like
|
||||||
uploading RDE dumps are only done for the live system. Cron tasks execute on
|
uploading RDE dumps are only done for the live system. Cron tasks execute on the
|
||||||
the `backend` service.
|
`backend` service.
|
||||||
|
|
||||||
Most cron tasks use the `TldFanoutAction` which is accessed via the
|
Most cron tasks use the `TldFanoutAction` which is accessed via the
|
||||||
`/_dr/cron/fanout` URL path. This action, which is run by the BackendServlet on
|
`/_dr/cron/fanout` URL path. This action, which is run by the BackendServlet on
|
||||||
the backend service, fans out a given cron task for each TLD that exists in the
|
the backend service, fans out a given cron task for each TLD that exists in the
|
||||||
registry system, using the queue that is specified in the `cron.xml` entry.
|
registry system, using the queue that is specified in the `cron.xml` entry.
|
||||||
Because some tasks may be computationally intensive and could risk spiking
|
Because some tasks may be computationally intensive and could risk spiking
|
||||||
system latency if all start executing immediately at the same time, there is a
|
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
|
`jitterSeconds` parameter that spreads out tasks over the given number of
|
||||||
seconds. This is used with DNS updates and commit log deletion.
|
seconds. This is used with DNS updates and commit log deletion.
|
||||||
|
|
||||||
The reason the `TldFanoutAction` exists is that a lot of tasks need to be done
|
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
|
separately for each TLD, such as RDE exports and NORDN uploads. It's simpler to
|
||||||
have a single cron entry that will create tasks for all TLDs than to have to
|
have a single cron entry that will create tasks for all TLDs than to have to
|
||||||
specify a separate cron task for each action for each TLD (though that is still
|
specify a separate cron task for each action for each TLD (though that is still
|
||||||
an option). Task queues also provide retry semantics in the event of transient
|
an option). Task queues also provide retry semantics in the event of transient
|
||||||
failures that a raw cron task does not. This is why there are some tasks that
|
failures that a raw cron task does not. This is why there are some tasks that do
|
||||||
do not fan out across TLDs that still use `TldFanoutAction` -- it's so that the
|
not fan out across TLDs that still use `TldFanoutAction` -- it's so that the
|
||||||
tasks retry in the face of transient errors.
|
tasks retry in the face of transient errors.
|
||||||
|
|
||||||
The full list of URL parameters to `TldFanoutAction` that can be specified in
|
The full list of URL parameters to `TldFanoutAction` that can be specified in
|
||||||
cron.xml is:
|
cron.xml is:
|
||||||
* `endpoint` -- The path of the action that should be executed (see `web.xml`).
|
|
||||||
* `queue` -- The cron queue to enqueue tasks in.
|
* `endpoint` -- The path of the action that should be executed (see
|
||||||
* `forEachRealTld` -- Specifies that the task should be run in each TLD of type
|
`web.xml`).
|
||||||
`REAL`. This can be combined with `forEachTestTld`.
|
* `queue` -- The cron queue to enqueue tasks in.
|
||||||
* `forEachTestTld` -- Specifies that the task should be run in each TLD of type
|
* `forEachRealTld` -- Specifies that the task should be run in each TLD of
|
||||||
`TEST`. This can be combined with `forEachRealTld`.
|
type `REAL`. This can be combined with `forEachTestTld`.
|
||||||
* `runInEmpty` -- Specifies that the task should be run globally, i.e. just
|
* `forEachTestTld` -- Specifies that the task should be run in each TLD of
|
||||||
once, rather than individually per TLD. This is provided to allow tasks to
|
type `TEST`. This can be combined with `forEachRealTld`.
|
||||||
retry. It is called "`runInEmpty`" for historical reasons.
|
* `runInEmpty` -- Specifies that the task should be run globally, i.e. just
|
||||||
* `excludes` -- A list of TLDs to exclude from processing.
|
once, rather than individually per TLD. This is provided to allow tasks to
|
||||||
* `jitterSeconds` -- The execution of each per-TLD task is delayed by a
|
retry. It is called "`runInEmpty`" for historical reasons.
|
||||||
different random number of seconds between zero and this max value.
|
* `excludes` -- A list of TLDs to exclude from processing.
|
||||||
|
* `jitterSeconds` -- The execution of each per-TLD task is delayed by a
|
||||||
|
different random number of seconds between zero and this max value.
|
||||||
|
|
||||||
## Cloud Datastore
|
## Cloud Datastore
|
||||||
|
|
||||||
The Domain Registry platform uses
|
The Domain Registry platform uses [Cloud Datastore]
|
||||||
[Cloud Datastore](https://cloud.google.com/appengine/docs/java/datastore/) as
|
(https://cloud.google.com/appengine/docs/java/datastore/) as its primary
|
||||||
its primary database. Cloud Datastore is a NoSQL document database that
|
database. Cloud Datastore is a NoSQL document database that provides automatic
|
||||||
provides automatic horizontal scaling, high performance, and high availability.
|
horizontal scaling, high performance, and high availability. All information
|
||||||
All information that is persisted to Cloud Datastore takes the form of Java
|
that is persisted to Cloud Datastore takes the form of Java classes annotated
|
||||||
classes annotated with `@Entity` that are located in the `model` package. The
|
with `@Entity` that are located in the `model` package. The [Objectify library]
|
||||||
[Objectify library](https://cloud.google.com/appengine/docs/java/gettingstarted/using-datastore-objectify)
|
(https://cloud.google.com/appengine/docs/java/gettingstarted/using-datastore-objectify)
|
||||||
is used to persist instances of these classes in a format that Datastore
|
is used to persist instances of these classes in a format that Datastore
|
||||||
understands.
|
understands.
|
||||||
|
|
||||||
A brief overview of the different entity types found in the App Engine Datastore
|
A brief overview of the different entity types found in the App Engine Datastore
|
||||||
Viewer may help administrators understand what they are seeing. Note that some
|
Viewer may help administrators understand what they are seeing. Note that some
|
||||||
of these entities are part of App Engine tools that are outside of the domain
|
of these entities are part of App Engine tools that are outside of the domain
|
||||||
registry codebase:
|
registry codebase:
|
||||||
|
|
||||||
* `_AE_*` -- These entities are created by App Engine.
|
* `_AE_*` -- These entities are created by App Engine.
|
||||||
* `_ah_SESSION` -- These entities track App Engine client sessions.
|
* `_ah_SESSION` -- These entities track App Engine client sessions.
|
||||||
* `_GAE_MR_*` -- These entities are generated by App Engine while running
|
* `_GAE_MR_*` -- These entities are generated by App Engine while running
|
||||||
MapReduces.
|
MapReduces.
|
||||||
* `BackupStatus` -- There should only be one of these entities, used to maintain
|
* `BackupStatus` -- There should only be one of these entities, used to
|
||||||
the state of the backup process.
|
maintain the state of the backup process.
|
||||||
* `Cancellation` -- A cancellation is a special type of billing event which
|
* `Cancellation` -- A cancellation is a special type of billing event which
|
||||||
represents the cancellation of another billing event such as a OneTime or
|
represents the cancellation of another billing event such as a OneTime or
|
||||||
Recurring.
|
Recurring.
|
||||||
* `ClaimsList`, `ClaimsListShard`, and `ClaimsListSingleton` -- These entities
|
* `ClaimsList`, `ClaimsListShard`, and `ClaimsListSingleton` -- These entities
|
||||||
store the TMCH claims list, for use in trademark processing.
|
store the TMCH claims list, for use in trademark processing.
|
||||||
* `CommitLog*` -- These entities store the commit log information.
|
* `CommitLog*` -- These entities store the commit log information.
|
||||||
* `ContactResource` -- These hold the ICANN contact information (but not
|
* `ContactResource` -- These hold the ICANN contact information (but not
|
||||||
registrar contacts, who have a separate entity type).
|
registrar contacts, who have a separate entity type).
|
||||||
* `Cursor` -- We use Cursor entities to maintain state about daily processes,
|
* `Cursor` -- We use Cursor entities to maintain state about daily processes,
|
||||||
remembering which dates have been processed. For instance, for the RDE export,
|
remembering which dates have been processed. For instance, for the RDE
|
||||||
Cursor entities maintain the date up to which each TLD has been exported.
|
export, Cursor entities maintain the date up to which each TLD has been
|
||||||
* `DomainApplicationIndex` -- These hold domain applications received during the
|
exported.
|
||||||
sunrise period.
|
* `DomainApplicationIndex` -- These hold domain applications received during
|
||||||
* `DomainBase` -- These hold the ICANN domain information.
|
the sunrise period.
|
||||||
* `DomainRecord` -- These are used during the DNS update process.
|
* `DomainBase` -- These hold the ICANN domain information.
|
||||||
* `EntityGroupRoot` -- There is only one EntityGroupRoot entity, which serves as
|
* `DomainRecord` -- These are used during the DNS update process.
|
||||||
the Datastore parent of many other entities.
|
* `EntityGroupRoot` -- There is only one EntityGroupRoot entity, which serves
|
||||||
* `EppResourceIndex` -- These entities allow enumeration of EPP resources (such
|
as the Datastore parent of many other entities.
|
||||||
as domains, hosts and contacts), which would otherwise be difficult to do in
|
* `EppResourceIndex` -- These entities allow enumeration of EPP resources
|
||||||
Datastore.
|
(such as domains, hosts and contacts), which would otherwise be difficult to
|
||||||
* `ExceptionReportEntity` -- These entities are generated automatically by
|
do in Datastore.
|
||||||
ECatcher, a Google-internal logging and debugging tool. Non-Google users
|
* `ExceptionReportEntity` -- These entities are generated automatically by
|
||||||
should not encounter these entries.
|
ECatcher, a Google-internal logging and debugging tool. Non-Google users
|
||||||
* `ForeignKeyContactIndex`, `ForeignKeyDomainIndex`, and `ForeignKeyHostIndex`
|
should not encounter these entries.
|
||||||
-- These act as a unique index on contacts, domains and hosts, allowing
|
* `ForeignKeyContactIndex`, `ForeignKeyDomainIndex`, and
|
||||||
transactional lookup by foreign key.
|
`ForeignKeyHostIndex` -- These act as a unique index on contacts, domains
|
||||||
* `HistoryEntry` -- A HistoryEntry is the record of a command which mutated an
|
and hosts, allowing transactional lookup by foreign key.
|
||||||
EPP resource. It serves as the parent of BillingEvents and PollMessages.
|
* `HistoryEntry` -- A HistoryEntry is the record of a command which mutated an
|
||||||
* `HostRecord` -- These are used during the DNS update process.
|
EPP resource. It serves as the parent of BillingEvents and PollMessages.
|
||||||
* `HostResource` -- These hold the ICANN host information.
|
* `HostRecord` -- These are used during the DNS update process.
|
||||||
* `Lock` -- Lock entities are used to control access to a shared resource such
|
* `HostResource` -- These hold the ICANN host information.
|
||||||
as an App Engine queue. Under ordinary circumstances, these locks will be
|
* `Lock` -- Lock entities are used to control access to a shared resource such
|
||||||
cleaned up automatically, and should not accumulate.
|
as an App Engine queue. Under ordinary circumstances, these locks will be
|
||||||
* `LogsExportCursor` -- This is a single entity which maintains the state of log
|
cleaned up automatically, and should not accumulate.
|
||||||
export.
|
* `LogsExportCursor` -- This is a single entity which maintains the state of
|
||||||
* `MR-*` -- These entities are generated by the App Engine MapReduce library in
|
log export.
|
||||||
the course of running MapReduces.
|
* `MR-*` -- These entities are generated by the App Engine MapReduce library
|
||||||
* `Modification` -- A Modification is a special type of billing event which
|
in the course of running MapReduces.
|
||||||
represents the modification of a OneTime billing event.
|
* `Modification` -- A Modification is a special type of billing event which
|
||||||
* `OneTime` -- A OneTime is a billing event which represents a one-time charge
|
represents the modification of a OneTime billing event.
|
||||||
or credit to the client (as opposed to Recurring).
|
* `OneTime` -- A OneTime is a billing event which represents a one-time charge
|
||||||
* `pipeline-*` -- These entities are also generated by the App Engine MapReduce
|
or credit to the client (as opposed to Recurring).
|
||||||
library.
|
* `pipeline-*` -- These entities are also generated by the App Engine
|
||||||
* `PollMessage` -- PollMessages are generated by the system to notify registrars
|
MapReduce library.
|
||||||
of asynchronous responses and status changes.
|
* `PollMessage` -- PollMessages are generated by the system to notify
|
||||||
* `PremiumList`, `PremiumListEntry`, and `PremiumListRevision` -- The standard
|
registrars of asynchronous responses and status changes.
|
||||||
method for determining which domain names receive premium pricing is to
|
* `PremiumList`, `PremiumListEntry`, and `PremiumListRevision` -- The standard
|
||||||
maintain a static list of premium names. Each PremiumList contains some number
|
method for determining which domain names receive premium pricing is to
|
||||||
of PremiumListRevisions, each of which in turn contains a PremiumListEntry for
|
maintain a static list of premium names. Each PremiumList contains some
|
||||||
each premium name.
|
number of PremiumListRevisions, each of which in turn contains a
|
||||||
* `RdeRevision` -- These entities are used by the RDE subsystem in the process
|
PremiumListEntry for each premium name.
|
||||||
of generating files.
|
* `RdeRevision` -- These entities are used by the RDE subsystem in the process
|
||||||
* `Recurring` -- A Recurring is a billing event which represents a recurring
|
of generating files.
|
||||||
charge to the client (as opposed to OneTime).
|
* `Recurring` -- A Recurring is a billing event which represents a recurring
|
||||||
* `Registrar` -- These hold information about client registrars.
|
charge to the client (as opposed to OneTime).
|
||||||
* `RegistrarContact` -- Registrars have contacts just as domains do. These are
|
* `Registrar` -- These hold information about client registrars.
|
||||||
stored in a special RegistrarContact entity.
|
* `RegistrarContact` -- Registrars have contacts just as domains do. These are
|
||||||
* `RegistrarCredit` and `RegistrarCreditBalance` -- The system supports the
|
stored in a special RegistrarContact entity.
|
||||||
concept of a registrar credit balance, which is a pool of credit that the
|
* `RegistrarCredit` and `RegistrarCreditBalance` -- The system supports the
|
||||||
registrar can use to offset amounts they owe. This might come from promotions,
|
concept of a registrar credit balance, which is a pool of credit that the
|
||||||
for instance. These entities maintain registrars' balances.
|
registrar can use to offset amounts they owe. This might come from
|
||||||
* `Registry` -- These hold information about the TLDs supported by the Registry
|
promotions, for instance. These entities maintain registrars' balances.
|
||||||
system.
|
* `Registry` -- These hold information about the TLDs supported by the
|
||||||
* `RegistryCursor` -- These entities are the predecessor to the Cursor
|
Registry system.
|
||||||
entities. We are no longer using them, and will be deleting them soon.
|
* `RegistryCursor` -- These entities are the predecessor to the Cursor
|
||||||
* `ReservedList` -- Each ReservedList entity represents an entire list of
|
entities. We are no longer using them, and will be deleting them soon.
|
||||||
reserved names which cannot be registered. Each TLD can have one or more
|
* `ReservedList` -- Each ReservedList entity represents an entire list of
|
||||||
attached reserved lists.
|
reserved names which cannot be registered. Each TLD can have one or more
|
||||||
* `ServerSecret` -- this is a single entity containing the secret numbers used
|
attached reserved lists.
|
||||||
for generating tokens such as XSRF tokens.
|
* `ServerSecret` -- this is a single entity containing the secret numbers used
|
||||||
* `SignedMarkRevocationList` -- The entities together contain the Signed Mark
|
for generating tokens such as XSRF tokens.
|
||||||
Data Revocation List file downloaded from the TMCH MarksDB each day. Each
|
* `SignedMarkRevocationList` -- The entities together contain the Signed Mark
|
||||||
entity contains up to 10,000 rows of the file, so depending on the size of the
|
Data Revocation List file downloaded from the TMCH MarksDB each day. Each
|
||||||
file, there will be some handful of entities.
|
entity contains up to 10,000 rows of the file, so depending on the size of
|
||||||
* `TmchCrl` -- This is a single entity containing ICANN's TMCH CA Certificate
|
the file, there will be some handful of entities.
|
||||||
Revocation List.
|
* `TmchCrl` -- This is a single entity containing ICANN's TMCH CA Certificate
|
||||||
|
Revocation List.
|
||||||
|
|
||||||
## Cloud Storage buckets
|
## Cloud Storage buckets
|
||||||
|
|
||||||
The Domain Registry platform uses
|
The Domain Registry platform uses [Cloud Storage]
|
||||||
[Cloud Storage](https://cloud.google.com/storage/) for bulk storage of large
|
(https://cloud.google.com/storage/) for bulk storage of large flat files that
|
||||||
flat files that aren't suitable for Datastore. These files include backups, RDE
|
aren't suitable for Datastore. These files include backups, RDE exports,
|
||||||
exports, Datastore snapshots (for ingestion into BigQuery), and reports. Each
|
Datastore snapshots (for ingestion into BigQuery), and reports. Each bucket name
|
||||||
bucket name must be unique across all of Google Cloud Storage, so we use the
|
must be unique across all of Google Cloud Storage, so we use the common
|
||||||
common recommended pattern of prefixing all buckets with the name of the App
|
recommended pattern of prefixing all buckets with the name of the App Engine app
|
||||||
Engine app (which is itself globally unique). Most of the bucket names are
|
(which is itself globally unique). Most of the bucket names are configurable,
|
||||||
configurable, but the defaults are as follows, with PROJECT standing in as a
|
but the defaults are as follows, with PROJECT standing in as a placeholder for
|
||||||
placeholder for the App Engine app name:
|
the App Engine app name:
|
||||||
|
|
||||||
* `PROJECT-billing` -- Monthly invoice files for each registrar.
|
* `PROJECT-billing` -- Monthly invoice files for each registrar.
|
||||||
* `PROJECT-commits` -- Daily exports of commit logs that are needed for
|
* `PROJECT-commits` -- Daily exports of commit logs that are needed for
|
||||||
potentially performing a restore.
|
potentially performing a restore.
|
||||||
* `PROJECT-domain-lists` -- Daily exports of all registered domain names per
|
* `PROJECT-domain-lists` -- Daily exports of all registered domain names per
|
||||||
TLD.
|
TLD.
|
||||||
* `PROJECT-gcs-logs` -- This bucket is used at Google to store the GCS access
|
* `PROJECT-gcs-logs` -- This bucket is used at Google to store the GCS access
|
||||||
logs and storage data. This bucket is not required by the Registry system,
|
logs and storage data. This bucket is not required by the Registry system,
|
||||||
but can provide useful logging information. For instructions on setup, see
|
but can provide useful logging information. For instructions on setup, see
|
||||||
the
|
the [Cloud Storage documentation]
|
||||||
[Cloud Storage documentation](https://cloud.google.com/storage/docs/access-logs).
|
(https://cloud.google.com/storage/docs/access-logs).
|
||||||
* `PROJECT-icann-brda` -- This bucket contains the weekly ICANN BRDA files.
|
* `PROJECT-icann-brda` -- This bucket contains the weekly ICANN BRDA files.
|
||||||
There is no lifecycle expiration; we keep a history of all the files. This
|
There is no lifecycle expiration; we keep a history of all the files. This
|
||||||
bucket must exist for the BRDA process to function.
|
bucket must exist for the BRDA process to function.
|
||||||
* `PROJECT-icann-zfa` -- This bucket contains the most recent ICANN ZFA
|
* `PROJECT-icann-zfa` -- This bucket contains the most recent ICANN ZFA files.
|
||||||
files. No lifecycle is needed, because the files are overwritten each time.
|
No lifecycle is needed, because the files are overwritten each time.
|
||||||
* `PROJECT-rde` -- This bucket contains RDE exports, which should then be
|
* `PROJECT-rde` -- This bucket contains RDE exports, which should then be
|
||||||
regularly uploaded to the escrow provider. Lifecycle is set to 90 days. The
|
regularly uploaded to the escrow provider. Lifecycle is set to 90 days. The
|
||||||
bucket must exist.
|
bucket must exist.
|
||||||
* `PROJECT-reporting` -- Contains monthly ICANN reporting files.
|
* `PROJECT-reporting` -- Contains monthly ICANN reporting files.
|
||||||
* `PROJECT-snapshots` -- Contains daily exports of Datastore entities of types
|
* `PROJECT-snapshots` -- Contains daily exports of Datastore entities of types
|
||||||
defined in `ExportConstants.java`. These are imported into BigQuery daily to
|
defined in `ExportConstants.java`. These are imported into BigQuery daily to
|
||||||
allow for in-depth querying.
|
allow for in-depth querying.
|
||||||
* `PROJECT.appspot.com` -- Temporary MapReduce files are stored here. By
|
* `PROJECT.appspot.com` -- Temporary MapReduce files are stored here. By
|
||||||
default, the App Engine MapReduce library places its temporary files in a
|
default, the App Engine MapReduce library places its temporary files in a
|
||||||
bucket named {project}.appspot.com. This bucket must exist. To keep temporary
|
bucket named {project}.appspot.com. This bucket must exist. To keep
|
||||||
files from building up, a 90-day or 180-day lifecycle should be applied to the
|
temporary files from building up, a 90-day or 180-day lifecycle should be
|
||||||
bucket, depending on how long you want to be able to go back and debug
|
applied to the bucket, depending on how long you want to be able to go back
|
||||||
MapReduce problems. At 30 GB per day of generate temporary files, this bucket
|
and debug MapReduce problems. At 30 GB per day of generate temporary files,
|
||||||
may be the largest consumer of storage, so only save what you actually use.
|
this bucket may be the largest consumer of storage, so only save what you
|
||||||
|
actually use.
|
||||||
|
|
||||||
## Commit logs
|
## Commit logs
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
There are multiple different kinds of configuration that go into getting a
|
There are multiple different kinds of configuration that go into getting a
|
||||||
working registry system up and running. Broadly speaking, configuration works
|
working registry system up and running. Broadly speaking, configuration works in
|
||||||
in two ways -- globally, for the entire sytem, and per-TLD. Global
|
two ways -- globally, for the entire sytem, and per-TLD. Global configuration is
|
||||||
configuration is managed by editing code and deploying a new version, whereas
|
managed by editing code and deploying a new version, whereas per-TLD
|
||||||
per-TLD configuration is data that lives in Datastore in `Registry` entities,
|
configuration is data that lives in Datastore in `Registry` entities, and is
|
||||||
and is updated by running `registry_tool` commands without having to deploy a
|
updated by running `registry_tool` commands without having to deploy a new
|
||||||
new version.
|
version.
|
||||||
|
|
||||||
## Environments
|
## Environments
|
||||||
|
|
||||||
Before getting into the details of configuration, it's important to note that a
|
Before getting into the details of configuration, it's important to note that a
|
||||||
lot of configuration is environment-dependent. It is common to see `switch`
|
lot of configuration is environment-dependent. It is common to see `switch`
|
||||||
statements that operate on the current `RegistryEnvironment`, and return
|
statements that operate on the current `RegistryEnvironment`, and return
|
||||||
different values for different environments. This is especially pronounced in
|
different values for different environments. This is especially pronounced in
|
||||||
the `UNITTEST` and `LOCAL` environments, which don't run on App Engine at all.
|
the `UNITTEST` and `LOCAL` environments, which don't run on App Engine at all.
|
||||||
As an example, some timeouts may be long in production and short in unit tests.
|
As an example, some timeouts may be long in production and short in unit tests.
|
||||||
|
|
||||||
|
@ -27,34 +27,34 @@ thoroughly documented in the [App Engine configuration docs][app-engine-config].
|
||||||
The main files of note that come pre-configured along with the domain registry
|
The main files of note that come pre-configured along with the domain registry
|
||||||
are:
|
are:
|
||||||
|
|
||||||
* `cron.xml` -- Configuration of cronjobs
|
* `cron.xml` -- Configuration of cronjobs
|
||||||
* `web.xml` -- Configuration of URL paths on the webserver
|
* `web.xml` -- Configuration of URL paths on the webserver
|
||||||
* `appengine-web.xml` -- Overall App Engine settings including number and type
|
* `appengine-web.xml` -- Overall App Engine settings including number and type
|
||||||
of instances
|
of instances
|
||||||
* `datastore-indexes.xml` -- Configuration of entity indexes in Datastore
|
* `datastore-indexes.xml` -- Configuration of entity indexes in Datastore
|
||||||
* `queue.xml` -- Configuration of App Engine task queues
|
* `queue.xml` -- Configuration of App Engine task queues
|
||||||
* `application.xml` -- Configuration of the application name and its services
|
* `application.xml` -- Configuration of the application name and its services
|
||||||
|
|
||||||
Cron, web, and queue are covered in more detail in the "App Engine architecture"
|
Cron, web, and queue are covered in more detail in the "App Engine architecture"
|
||||||
doc, and the rest are covered in the general App Engine documentation.
|
doc, and the rest are covered in the general App Engine documentation.
|
||||||
|
|
||||||
If you are not writing new code to implement custom features, is unlikely that
|
If you are not writing new code to implement custom features, is unlikely that
|
||||||
you will need to make any modifications beyond simple changes to
|
you will need to make any modifications beyond simple changes to
|
||||||
`application.xml` and `appengine-web.xml`. If you are writing new features,
|
`application.xml` and `appengine-web.xml`. If you are writing new features, it's
|
||||||
it's likely you'll need to add cronjobs, URL paths, Datastore indexes, and task
|
likely you'll need to add cronjobs, URL paths, Datastore indexes, and task
|
||||||
queues, and thus edit those associated XML files.
|
queues, and thus edit those associated XML files.
|
||||||
|
|
||||||
## Global configuration
|
## Global configuration
|
||||||
|
|
||||||
There are two different mechanisms by which global configuration is managed:
|
There are two different mechanisms by which global configuration is managed:
|
||||||
`RegistryConfig` (the old way) and `ConfigModule` (the new way). Ideally there
|
`RegistryConfig` (the old way) and `ConfigModule` (the new way). Ideally there
|
||||||
would just be one, but the required code cleanup that hasn't been completed yet.
|
would just be one, but the required code cleanup that hasn't been completed yet.
|
||||||
If you are adding new options, prefer adding them to `ConfigModule`.
|
If you are adding new options, prefer adding them to `ConfigModule`.
|
||||||
|
|
||||||
**`RegistryConfig`** is an interface, of which you write an implementing class
|
**`RegistryConfig`** is an interface, of which you write an implementing class
|
||||||
containing the configuration values. `RegistryConfigLoader` is the class that
|
containing the configuration values. `RegistryConfigLoader` is the class that
|
||||||
provides the instance of `RegistryConfig`, and defaults to returning
|
provides the instance of `RegistryConfig`, and defaults to returning
|
||||||
`ProductionRegistryConfigExample`. In order to create a configuration specific
|
`ProductionRegistryConfigExample`. In order to create a configuration specific
|
||||||
to your registry, we recommend copying the `ProductionRegistryConfigExample`
|
to your registry, we recommend copying the `ProductionRegistryConfigExample`
|
||||||
class to a new class that will not be shared publicly, setting the
|
class to a new class that will not be shared publicly, setting the
|
||||||
`com.google.domain.registry.config` system property in `appengine-web.xml` to
|
`com.google.domain.registry.config` system property in `appengine-web.xml` to
|
||||||
|
@ -64,16 +64,16 @@ configuration options.
|
||||||
|
|
||||||
The `RegistryConfig` class has documentation on all of the methods that should
|
The `RegistryConfig` class has documentation on all of the methods that should
|
||||||
be sufficient to explain what each option is, and
|
be sufficient to explain what each option is, and
|
||||||
`ProductionRegistryConfigExample` provides an example value for each one. Some
|
`ProductionRegistryConfigExample` provides an example value for each one. Some
|
||||||
example configuration options in this interface include the App Engine project
|
example configuration options in this interface include the App Engine project
|
||||||
ID, the number of days to retain commit logs, the names of various Cloud Storage
|
ID, the number of days to retain commit logs, the names of various Cloud Storage
|
||||||
bucket names, and URLs for some required services both external and internal.
|
bucket names, and URLs for some required services both external and internal.
|
||||||
|
|
||||||
**`ConfigModule`** is a Dagger module that provides injectable configuration
|
**`ConfigModule`** is a Dagger module that provides injectable configuration
|
||||||
options (some of which come from `RegistryConfig` above, but most of which do
|
options (some of which come from `RegistryConfig` above, but most of which do
|
||||||
not). This is preferred over `RegistryConfig` for new configuration options
|
not). This is preferred over `RegistryConfig` for new configuration options
|
||||||
because being able to inject configuration options is a nicer pattern that makes
|
because being able to inject configuration options is a nicer pattern that makes
|
||||||
for cleaner code. Some configuration options that can be changed in this class
|
for cleaner code. Some configuration options that can be changed in this class
|
||||||
include timeout lengths and buffer sizes for various tasks, email addresses and
|
include timeout lengths and buffer sizes for various tasks, email addresses and
|
||||||
URLs to use for various services, more Cloud Storage bucket names, and WHOIS
|
URLs to use for various services, more Cloud Storage bucket names, and WHOIS
|
||||||
disclaimer text.
|
disclaimer text.
|
||||||
|
@ -83,39 +83,39 @@ disclaimer text.
|
||||||
Some configuration values, such as PGP private keys, are so sensitive that they
|
Some configuration values, such as PGP private keys, are so sensitive that they
|
||||||
should not be written in code as per the configuration methods above, as that
|
should not be written in code as per the configuration methods above, as that
|
||||||
would pose too high a risk of them accidentally being leaked, e.g. in a source
|
would pose too high a risk of them accidentally being leaked, e.g. in a source
|
||||||
control mishap. We use a secret store to persist these values in a secure
|
control mishap. We use a secret store to persist these values in a secure
|
||||||
manner, and abstract access to them using the `Keyring` interface.
|
manner, and abstract access to them using the `Keyring` interface.
|
||||||
|
|
||||||
The `Keyring` interface contains methods for all sensitive configuration values,
|
The `Keyring` interface contains methods for all sensitive configuration values,
|
||||||
which are primarily credentials used to access various ICANN and ICANN-
|
which are primarily credentials used to access various ICANN and ICANN-
|
||||||
affiliated services (such as RDE). These values are only needed for real
|
affiliated services (such as RDE). These values are only needed for real
|
||||||
production registries and PDT environments. If you are just playing around with
|
production registries and PDT environments. If you are just playing around with
|
||||||
the platform at first, it is OK to put off defining these values until
|
the platform at first, it is OK to put off defining these values until
|
||||||
necessary. To that end, a `DummyKeyringModule` is included that simply provides
|
necessary. To that end, a `DummyKeyringModule` is included that simply provides
|
||||||
an `InMemoryKeyring` populated with dummy values for all secret keys. This
|
an `InMemoryKeyring` populated with dummy values for all secret keys. This
|
||||||
allows the codebase to compile and run, but of course any actions that attempt
|
allows the codebase to compile and run, but of course any actions that attempt
|
||||||
to connect to external services will fail because none of the keys are real.
|
to connect to external services will fail because none of the keys are real.
|
||||||
|
|
||||||
To configure a production registry system, you will need to write a replacement
|
To configure a production registry system, you will need to write a replacement
|
||||||
module for `DummyKeyringModule` that loads the credentials in a secure way, and
|
module for `DummyKeyringModule` that loads the credentials in a secure way, and
|
||||||
provides them using either an instance of `InMemoryKeyring` or your own custom
|
provides them using either an instance of `InMemoryKeyring` or your own custom
|
||||||
implementation of `Keyring`. You then need to replace all usages of
|
implementation of `Keyring`. You then need to replace all usages of
|
||||||
`DummyKeyringModule` with your own module in all of the per-service components
|
`DummyKeyringModule` with your own module in all of the per-service components
|
||||||
in which it is referenced. The functions in `PgpHelper` will likely prove
|
in which it is referenced. The functions in `PgpHelper` will likely prove useful
|
||||||
useful for loading keys stored in PGP format into the PGP key classes that
|
for loading keys stored in PGP format into the PGP key classes that you'll need
|
||||||
you'll need to provide from `Keyring`, and you can see examples of them in
|
to provide from `Keyring`, and you can see examples of them in action in
|
||||||
action in `DummyKeyringModule`.
|
`DummyKeyringModule`.
|
||||||
|
|
||||||
## Per-TLD configuration
|
## Per-TLD configuration
|
||||||
|
|
||||||
`Registry` entities, which are persisted to Datastore, are used for per-TLD
|
`Registry` entities, which are persisted to Datastore, are used for per-TLD
|
||||||
configuration. They contain any kind of configuration that is specific to a
|
configuration. They contain any kind of configuration that is specific to a TLD,
|
||||||
TLD, such as the create/renew price of a domain name, the pricing engine
|
such as the create/renew price of a domain name, the pricing engine
|
||||||
implementation, the DNS writer implementation, whether escrow exports are
|
implementation, the DNS writer implementation, whether escrow exports are
|
||||||
enabled, the default currency, the reserved label lists, and more. The
|
enabled, the default currency, the reserved label lists, and more. The
|
||||||
`update_tld` command in `registry_tool` is used to set all of these options.
|
`update_tld` command in `registry_tool` is used to set all of these options. See
|
||||||
See the "Registry tool" documentation for more information, as well as the
|
the "Registry tool" documentation for more information, as well as the
|
||||||
command-line help for the `update_tld` command. Unlike global configuration
|
command-line help for the `update_tld` command. Unlike global configuration
|
||||||
above, per-TLD configuration options are stored as data in the running system,
|
above, per-TLD configuration options are stored as data in the running system,
|
||||||
and thus do not require code pushes to update.
|
and thus do not require code pushes to update.
|
||||||
|
|
||||||
|
|
127
docs/install.md
127
docs/install.md
|
@ -5,25 +5,27 @@ working running instance.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
* A recent version of the
|
* A recent version of the [Java 7 JDK]
|
||||||
[Java 7 JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html)
|
(http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html)
|
||||||
(note that Java 8 support should be coming to App Engine soon).
|
(note that Java 8 support should be coming to App Engine soon).
|
||||||
* [Bazel](http://bazel.io/), which is the buld system that
|
* [Bazel](http://bazel.io/), which is the buld system that the Domain Registry
|
||||||
the Domain Registry project uses. The minimum required version is 0.3.1.
|
project uses. The minimum required version is 0.3.1.
|
||||||
* [Google App Engine SDK for Java](https://cloud.google.com/appengine/downloads#Google_App_Engine_SDK_for_Java),
|
* [Google App Engine SDK for Java]
|
||||||
especially `appcfg`, which is a command-line tool that runs locally that is used
|
(https://cloud.google.com/appengine/downloads#Google_App_Engine_SDK_for_Java),
|
||||||
to communicate with the App Engine cloud.
|
especially `appcfg`, which is a command-line tool that runs locally that is
|
||||||
* [Create an application](https://cloud.google.com/appengine/docs/java/quickstart)
|
used to communicate with the App Engine cloud.
|
||||||
on App Engine to deploy to, and set up `appcfg` to connect to it.
|
* [Create an application]
|
||||||
|
(https://cloud.google.com/appengine/docs/java/quickstart) on App Engine to
|
||||||
|
deploy to, and set up `appcfg` to connect to it.
|
||||||
|
|
||||||
## Downloading the code
|
## Downloading the code
|
||||||
|
|
||||||
Start off by grabbing the latest version from the
|
Start off by grabbing the latest version from the [Domain Registry project on
|
||||||
[Domain Registry project on GitHub](https://github.com/google/domain-registry).
|
GitHub](https://github.com/google/domain-registry). This can be done either by
|
||||||
This can be done either by cloning the Git repo (if you expect to make code
|
cloning the Git repo (if you expect to make code changes to contribute back), or
|
||||||
changes to contribute back), or simply by downloading the latest release as a
|
simply by downloading the latest release as a zip file. This guide will cover
|
||||||
zip file. This guide will cover cloning from Git, but should work almost
|
cloning from Git, but should work almost identically for downloading the zip
|
||||||
identically for downloading the zip file.
|
file.
|
||||||
|
|
||||||
$ git clone git@github.com:google/domain-registry.git
|
$ git clone git@github.com:google/domain-registry.git
|
||||||
Cloning into 'domain-registry'...
|
Cloning into 'domain-registry'...
|
||||||
|
@ -36,19 +38,19 @@ identically for downloading the zip file.
|
||||||
|
|
||||||
The most important directories are:
|
The most important directories are:
|
||||||
|
|
||||||
* `docs` -- the documentation (including this install guide)
|
* `docs` -- the documentation (including this install guide)
|
||||||
* `java/google/registry` -- all of the source code of the main project
|
* `java/google/registry` -- all of the source code of the main project
|
||||||
* `javatests/google/registry` -- all of the tests for the project
|
* `javatests/google/registry` -- all of the tests for the project
|
||||||
* `python` -- Some Python reporting scripts
|
* `python` -- Some Python reporting scripts
|
||||||
* `scripts` -- Scripts for configuring development environments
|
* `scripts` -- Scripts for configuring development environments
|
||||||
|
|
||||||
Everything else, especially `third_party`, contains dependencies that are used
|
Everything else, especially `third_party`, contains dependencies that are used
|
||||||
by the project.
|
by the project.
|
||||||
|
|
||||||
## Building and verifying the code
|
## Building and verifying the code
|
||||||
|
|
||||||
The first step is to verify that the project successfully builds. This will
|
The first step is to verify that the project successfully builds. This will also
|
||||||
also download and install dependencies.
|
download and install dependencies.
|
||||||
|
|
||||||
$ bazel --batch build //java{,tests}/google/registry/...
|
$ bazel --batch build //java{,tests}/google/registry/...
|
||||||
INFO: Found 584 targets...
|
INFO: Found 584 targets...
|
||||||
|
@ -56,7 +58,7 @@ also download and install dependencies.
|
||||||
INFO: Elapsed time: 124.433s, Critical Path: 116.92s
|
INFO: Elapsed time: 124.433s, Critical Path: 116.92s
|
||||||
|
|
||||||
There may be some warnings thrown, but if there are no errors, then you are good
|
There may be some warnings thrown, but if there are no errors, then you are good
|
||||||
to go. Next, run the tests to verify that everything works properly. The tests
|
to go. Next, run the tests to verify that everything works properly. The tests
|
||||||
can be pretty resource intensive, so experiment with different values of
|
can be pretty resource intensive, so experiment with different values of
|
||||||
parameters to optimize between low running time and not slowing down your
|
parameters to optimize between low running time and not slowing down your
|
||||||
computer too badly.
|
computer too badly.
|
||||||
|
@ -68,10 +70,10 @@ computer too badly.
|
||||||
## Running a development instance locally
|
## Running a development instance locally
|
||||||
|
|
||||||
`RegistryTestServer` is a lightweight test server for the registry that is
|
`RegistryTestServer` is a lightweight test server for the registry that is
|
||||||
suitable for running locally for development. It uses local versions of all
|
suitable for running locally for development. It uses local versions of all
|
||||||
Google Cloud Platform dependencies, when available. Correspondingly, its
|
Google Cloud Platform dependencies, when available. Correspondingly, its
|
||||||
functionality is limited compared to a Domain Registry instance running on an
|
functionality is limited compared to a Domain Registry instance running on an
|
||||||
actual App Engine instance. To see its command-line parameters, run:
|
actual App Engine instance. To see its command-line parameters, run:
|
||||||
|
|
||||||
$ bazel run //javatests/google/registry/server -- --help
|
$ bazel run //javatests/google/registry/server -- --help
|
||||||
|
|
||||||
|
@ -86,13 +88,13 @@ http://localhost:8080/registrar .
|
||||||
## Deploying the code
|
## Deploying the code
|
||||||
|
|
||||||
You are going to need to configure a variety of things before a working
|
You are going to need to configure a variety of things before a working
|
||||||
installation can be deployed (see the Configuration guide for that). It's
|
installation can be deployed (see the Configuration guide for that). It's
|
||||||
recommended to at least confirm that the default version of the code can be
|
recommended to at least confirm that the default version of the code can be
|
||||||
pushed at all first before diving into that, with the expectation that things
|
pushed at all first before diving into that, with the expectation that things
|
||||||
won't work properly until they are configured.
|
won't work properly until they are configured.
|
||||||
|
|
||||||
All of the [EAR](https://en.wikipedia.org/wiki/EAR_(file_format)) and
|
All of the [EAR](https://en.wikipedia.org/wiki/EAR_\(file_format\)) and [WAR]
|
||||||
[WAR](https://en.wikipedia.org/wiki/WAR_(file_format)) files for the different
|
(https://en.wikipedia.org/wiki/WAR_\(file_format\)) files for the different
|
||||||
environments, which were built in the previous step, are outputted to the
|
environments, which were built in the previous step, are outputted to the
|
||||||
`bazel-genfiles` directory as follows:
|
`bazel-genfiles` directory as follows:
|
||||||
|
|
||||||
|
@ -115,7 +117,8 @@ an environment in the file name), whereas there is one WAR file per service per
|
||||||
environment, with there being three services in total: default, backend, and
|
environment, with there being three services in total: default, backend, and
|
||||||
tools.
|
tools.
|
||||||
|
|
||||||
Then, use `appcfg` to [deploy the WAR files](https://cloud.google.com/appengine/docs/java/tools/uploadinganapp):
|
Then, use `appcfg` to [deploy the WAR files]
|
||||||
|
(https://cloud.google.com/appengine/docs/java/tools/uploadinganapp):
|
||||||
|
|
||||||
$ cd /path/to/downloaded/appengine/app
|
$ cd /path/to/downloaded/appengine/app
|
||||||
$ /path/to/appcfg.sh update /path/to/registry_default.war
|
$ /path/to/appcfg.sh update /path/to/registry_default.war
|
||||||
|
@ -126,15 +129,15 @@ Then, use `appcfg` to [deploy the WAR files](https://cloud.google.com/appengine/
|
||||||
|
|
||||||
Once the code is deployed, the next step is to play around with creating some
|
Once the code is deployed, the next step is to play around with creating some
|
||||||
entities in the registry, including a TLD, a registrar, a domain, a contact, and
|
entities in the registry, including a TLD, a registrar, a domain, a contact, and
|
||||||
a host. Note: Do this on a non-production environment! All commands below use
|
a host. Note: Do this on a non-production environment! All commands below use
|
||||||
`registry_tool` to interact with the running registry system; see the
|
`registry_tool` to interact with the running registry system; see the
|
||||||
documentation on `registry_tool` for additional information on it. We'll assume
|
documentation on `registry_tool` for additional information on it. We'll assume
|
||||||
that all commands below are running in the `alpha` environment; if you named
|
that all commands below are running in the `alpha` environment; if you named
|
||||||
your environment differently, then use that everywhere that `alpha` appears.
|
your environment differently, then use that everywhere that `alpha` appears.
|
||||||
|
|
||||||
### Create a TLD
|
### Create a TLD
|
||||||
|
|
||||||
Pick the name of a TLD to create. For the purposes of this example we'll use
|
Pick the name of a TLD to create. For the purposes of this example we'll use
|
||||||
"example", which conveniently happens to be an ICANN reserved string, meaning
|
"example", which conveniently happens to be an ICANN reserved string, meaning
|
||||||
it'll never be created for real on the Internet at large.
|
it'll never be created for real on the Internet at large.
|
||||||
|
|
||||||
|
@ -144,25 +147,25 @@ it'll never be created for real on the Internet at large.
|
||||||
Perform this command? (y/N): y
|
Perform this command? (y/N): y
|
||||||
Updated 1 entities.
|
Updated 1 entities.
|
||||||
|
|
||||||
The name of the TLD is the main parameter passed to the command. The initial
|
The name of the TLD is the main parameter passed to the command. The initial TLD
|
||||||
TLD state is set here to general availability, bypassing sunrise and landrush,
|
state is set here to general availability, bypassing sunrise and landrush, so
|
||||||
so that domain names can be created immediately in the following steps. The TLD
|
that domain names can be created immediately in the following steps. The TLD
|
||||||
type is set to `TEST` (the other alternative being `REAL`) for obvious reasons.
|
type is set to `TEST` (the other alternative being `REAL`) for obvious reasons.
|
||||||
|
|
||||||
`roid_suffix` is the suffix that will be used for repository ids of domains on
|
`roid_suffix` is the suffix that will be used for repository ids of domains on
|
||||||
the TLD -- it must be all uppercase and a maximum of eight ASCII characters.
|
the TLD -- it must be all uppercase and a maximum of eight ASCII characters.
|
||||||
ICANN
|
ICANN [recommends]
|
||||||
[recommends](https://www.icann.org/resources/pages/correction-non-compliant-roids-2015-08-26-en)
|
(https://www.icann.org/resources/pages/correction-non-compliant-roids-2015-08-26-en)
|
||||||
a unique ROID suffix per TLD. The easiest way to come up with one is to simply
|
a unique ROID suffix per TLD. The easiest way to come up with one is to simply
|
||||||
use the entire uppercased TLD string if it is eight characters or fewer, or
|
use the entire uppercased TLD string if it is eight characters or fewer, or
|
||||||
abbreviate it in some sensible way down to eight if it is longer. The full repo
|
abbreviate it in some sensible way down to eight if it is longer. The full repo
|
||||||
id of a domain resource is a hex string followed by the suffix,
|
id of a domain resource is a hex string followed by the suffix, e.g.
|
||||||
e.g. `12F7CDF3-EXAMPLE` for our example TLD.
|
`12F7CDF3-EXAMPLE` for our example TLD.
|
||||||
|
|
||||||
### Create a registrar
|
### Create a registrar
|
||||||
|
|
||||||
Now we need to create a registrar and give it access to operate on the example
|
Now we need to create a registrar and give it access to operate on the example
|
||||||
TLD. For the purposes of our example we'll name the registrar "Acme".
|
TLD. For the purposes of our example we'll name the registrar "Acme".
|
||||||
|
|
||||||
$ registry_tool -e alpha create_registrar acme --name 'ACME Corp' \
|
$ registry_tool -e alpha create_registrar acme --name 'ACME Corp' \
|
||||||
--registrar_type TEST --password hunter2 \
|
--registrar_type TEST --password hunter2 \
|
||||||
|
@ -175,27 +178,27 @@ TLD. For the purposes of our example we'll name the registrar "Acme".
|
||||||
support it.
|
support it.
|
||||||
|
|
||||||
In the command above, "acme" is the internal registrar id that is the primary
|
In the command above, "acme" is the internal registrar id that is the primary
|
||||||
key used to refer to the registrar. The `name` is the display name that is used
|
key used to refer to the registrar. The `name` is the display name that is used
|
||||||
less often, primarily in user interfaces. We again set the type of the resource
|
less often, primarily in user interfaces. We again set the type of the resource
|
||||||
here to `TEST`. The `password` is the EPP password that the registrar uses to
|
here to `TEST`. The `password` is the EPP password that the registrar uses to
|
||||||
log in with. The `icann_referral_email` is the email address associated with
|
log in with. The `icann_referral_email` is the email address associated with the
|
||||||
the initial creation of the registrar -- note that the registrar cannot change
|
initial creation of the registrar -- note that the registrar cannot change it
|
||||||
it later. The address fields are self-explanatory (note that other parameters
|
later. The address fields are self-explanatory (note that other parameters are
|
||||||
are available for international addresses). The `allowed_tlds` parameter is a
|
available for international addresses). The `allowed_tlds` parameter is a
|
||||||
comma-delimited list of TLDs that the registrar has access to, and here is set
|
comma-delimited list of TLDs that the registrar has access to, and here is set
|
||||||
to the example TLD.
|
to the example TLD.
|
||||||
|
|
||||||
### Create a contact
|
### Create a contact
|
||||||
|
|
||||||
Now we want to create a contact, as a contact is required before a domain can be
|
Now we want to create a contact, as a contact is required before a domain can be
|
||||||
created. Contacts can be used on any number of domains across any number of
|
created. Contacts can be used on any number of domains across any number of
|
||||||
TLDs, and contain the information on who owns or provides technical support for
|
TLDs, and contain the information on who owns or provides technical support for
|
||||||
a TLD. These details will appear in WHOIS queries. Note the `-c` parameter,
|
a TLD. These details will appear in WHOIS queries. Note the `-c` parameter,
|
||||||
which stands for client identifier: This is used on most `registry_tool`
|
which stands for client identifier: This is used on most `registry_tool`
|
||||||
commands, and is used to specify the id of the registrar that the command will
|
commands, and is used to specify the id of the registrar that the command will
|
||||||
be executed using. Contact, domain, and host creation all work by constructing
|
be executed using. Contact, domain, and host creation all work by constructing
|
||||||
an EPP message that is sent to the registry, and EPP commands need to run under
|
an EPP message that is sent to the registry, and EPP commands need to run under
|
||||||
the context of a registrar. The "acme" registrar that was created above is used
|
the context of a registrar. The "acme" registrar that was created above is used
|
||||||
for this purpose.
|
for this purpose.
|
||||||
|
|
||||||
$ registry_tool -e alpha create_contact -c acme --id abcd1234 \
|
$ registry_tool -e alpha create_contact -c acme --id abcd1234 \
|
||||||
|
@ -204,24 +207,24 @@ for this purpose.
|
||||||
[ ... snip EPP response ... ]
|
[ ... snip EPP response ... ]
|
||||||
|
|
||||||
The `id` is the contact id, and is referenced elsewhere in the system (e.g. when
|
The `id` is the contact id, and is referenced elsewhere in the system (e.g. when
|
||||||
a domain is created and the admin contact is specified). The `name` is the
|
a domain is created and the admin contact is specified). The `name` is the
|
||||||
display name of the contact, which is usually the name of a company or of a
|
display name of the contact, which is usually the name of a company or of a
|
||||||
person. Again, the address fields are required, along with an `email`.
|
person. Again, the address fields are required, along with an `email`.
|
||||||
|
|
||||||
### Create a host
|
### Create a host
|
||||||
|
|
||||||
Hosts are used to specify the IP addresses (either v4 or v6) that are associated
|
Hosts are used to specify the IP addresses (either v4 or v6) that are associated
|
||||||
with a given nameserver. Note that hosts may either be in-bailiwick (on a TLD
|
with a given nameserver. Note that hosts may either be in-bailiwick (on a TLD
|
||||||
that this registry runs) or out-of-bailiwick. In-bailiwick hosts may
|
that this registry runs) or out-of-bailiwick. In-bailiwick hosts may
|
||||||
additionally be subordinate (a subdomain of a domain name that is on this
|
additionally be subordinate (a subdomain of a domain name that is on this
|
||||||
registry). Let's create an out-of-bailiwick nameserver, which is the simplest
|
registry). Let's create an out-of-bailiwick nameserver, which is the simplest
|
||||||
type.
|
type.
|
||||||
|
|
||||||
$ my_registry_tool -e alpha create_host -c acme --host ns1.google.com
|
$ my_registry_tool -e alpha create_host -c acme --host ns1.google.com
|
||||||
[ ... snip EPP response ... ]
|
[ ... snip EPP response ... ]
|
||||||
|
|
||||||
Note that hosts are required to have IP addresses if they are subordinate, and
|
Note that hosts are required to have IP addresses if they are subordinate, and
|
||||||
must not have IP addresses if they are not subordinate. Use the `--addresses`
|
must not have IP addresses if they are not subordinate. Use the `--addresses`
|
||||||
parameter to set the IP addresses on a host, passing in a comma-delimited list
|
parameter to set the IP addresses on a host, passing in a comma-delimited list
|
||||||
of IP addresses in either IPv4 or IPv6 format.
|
of IP addresses in either IPv4 or IPv6 format.
|
||||||
|
|
||||||
|
@ -236,7 +239,7 @@ and host.
|
||||||
[ ... snip EPP response ... ]
|
[ ... snip EPP response ... ]
|
||||||
|
|
||||||
Note how the same contact id (from above) is used for the administrative,
|
Note how the same contact id (from above) is used for the administrative,
|
||||||
technical, and registrant contact. This is quite common on domain names.
|
technical, and registrant contact. This is quite common on domain names.
|
||||||
|
|
||||||
To verify that everything worked, let's query the WHOIS information for
|
To verify that everything worked, let's query the WHOIS information for
|
||||||
fake.example:
|
fake.example:
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Registry tool
|
# Registry tool
|
||||||
|
|
||||||
The registry tool is a command-line registry administration tool that is invoked
|
The registry tool is a command-line registry administration tool that is invoked
|
||||||
using the `registry_tool` command. It has the ability to view and change a
|
using the `registry_tool` command. It has the ability to view and change a large
|
||||||
large number of things in a running domain registry environment, including
|
number of things in a running domain registry environment, including creating
|
||||||
creating registrars, updating premium and reserved lists, running an EPP command
|
registrars, updating premium and reserved lists, running an EPP command from a
|
||||||
from a given XML file, and performing various backend tasks like re-running RDE
|
given XML file, and performing various backend tasks like re-running RDE if the
|
||||||
if the most recent export failed. Its code lives inside the tools package
|
most recent export failed. Its code lives inside the tools package
|
||||||
(`java/google/registry/tools`), and is compiled by building the `registry_tool`
|
(`java/google/registry/tools`), and is compiled by building the `registry_tool`
|
||||||
target in the Bazel BUILD file in that package.
|
target in the Bazel BUILD file in that package.
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ To build the tool and display its command-line help, execute this command:
|
||||||
|
|
||||||
For future invocations you should alias the compiled binary in the
|
For future invocations you should alias the compiled binary in the
|
||||||
`bazel-genfiles/java/google/registry` directory or add it to your path so that
|
`bazel-genfiles/java/google/registry` directory or add it to your path so that
|
||||||
you can run it more easily. The rest of this guide assumes that it has been
|
you can run it more easily. The rest of this guide assumes that it has been
|
||||||
aliased to `registry_tool`.
|
aliased to `registry_tool`.
|
||||||
|
|
||||||
The registry tool is always called with a specific environment to run in using
|
The registry tool is always called with a specific environment to run in using
|
||||||
the -e parameter. This looks like:
|
the -e parameter. This looks like:
|
||||||
|
|
||||||
$ registry_tool -e production {command name} {command parameters}
|
$ registry_tool -e production {command name} {command parameters}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ There are actually two separate tools, `gtech_tool`, which is a collection of
|
||||||
lower impact commands intended to be used by tech support personnel, and
|
lower impact commands intended to be used by tech support personnel, and
|
||||||
`registry_tool`, which is a superset of `gtech_tool` that contains additional
|
`registry_tool`, which is a superset of `gtech_tool` that contains additional
|
||||||
commands that are potentially more destructive and can change more aspects of
|
commands that are potentially more destructive and can change more aspects of
|
||||||
the system. A full list of `gtech_tool` commands can be found in
|
the system. A full list of `gtech_tool` commands can be found in
|
||||||
`GtechTool.java`, and the additional commands that only `registry_tool` has
|
`GtechTool.java`, and the additional commands that only `registry_tool` has
|
||||||
access to are in `RegistryTool.java`.
|
access to are in `RegistryTool.java`.
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ There are two broad ways that commands are implemented: some that send requests
|
||||||
to `ToolsServlet` to execute the action on the server (these commands implement
|
to `ToolsServlet` to execute the action on the server (these commands implement
|
||||||
`ServerSideCommand`), and others that execute the command locally using the
|
`ServerSideCommand`), and others that execute the command locally using the
|
||||||
[Remote API](https://cloud.google.com/appengine/docs/java/tools/remoteapi)
|
[Remote API](https://cloud.google.com/appengine/docs/java/tools/remoteapi)
|
||||||
(these commands implement `RemoteApiCommand`). Server-side commands take more
|
(these commands implement `RemoteApiCommand`). Server-side commands take more
|
||||||
work to implement because they require both a client and a server-side
|
work to implement because they require both a client and a server-side
|
||||||
component, e.g. `CreatePremiumListCommand.java` and
|
component, e.g. `CreatePremiumListCommand.java` and
|
||||||
`CreatePremiumListAction.java` respectively for creating a premium list.
|
`CreatePremiumListAction.java` respectively for creating a premium list.
|
||||||
|
@ -56,35 +56,36 @@ Engine, including running a large MapReduce, because they execute on the tools
|
||||||
service in the App Engine cloud.
|
service in the App Engine cloud.
|
||||||
|
|
||||||
Local commands, by contrast, are easier to implement, because there is only a
|
Local commands, by contrast, are easier to implement, because there is only a
|
||||||
local component to write, but they aren't as powerful. A general rule of thumb
|
local component to write, but they aren't as powerful. A general rule of thumb
|
||||||
for making this determination is to use a local command if possible, or a
|
for making this determination is to use a local command if possible, or a
|
||||||
server-side command otherwise.
|
server-side command otherwise.
|
||||||
|
|
||||||
## Common tool patterns
|
## Common tool patterns
|
||||||
|
|
||||||
All tools ultimately implement the `Command` interface located in the `tools`
|
All tools ultimately implement the `Command` interface located in the `tools`
|
||||||
package. If you use an IDE such as Eclipse to view the type hierarchy of that
|
package. If you use an IDE such as Eclipse to view the type hierarchy of that
|
||||||
interface, you'll see all of the commands that exist, as well as how a lot of
|
interface, you'll see all of the commands that exist, as well as how a lot of
|
||||||
them are grouped using sub-interfaces or abstract classes that provide
|
them are grouped using sub-interfaces or abstract classes that provide
|
||||||
additional functionality. The most common patterns that are used by a large
|
additional functionality. The most common patterns that are used by a large
|
||||||
number of other tools are:
|
number of other tools are:
|
||||||
|
|
||||||
* **`BigqueryCommand`** -- Provides a connection to BigQuery for tools that need
|
* **`BigqueryCommand`** -- Provides a connection to BigQuery for tools that
|
||||||
it.
|
need it.
|
||||||
* **`ConfirmingCommand`** -- Provides the methods `prompt()` and `execute()` to
|
* **`ConfirmingCommand`** -- Provides the methods `prompt()` and `execute()`
|
||||||
override. `prompt()` outputs a message (usually what the command is going to
|
to override. `prompt()` outputs a message (usually what the command is going
|
||||||
do) and prompts the user to confirm execution of the command, and then
|
to do) and prompts the user to confirm execution of the command, and then
|
||||||
`execute()` actually does it.
|
`execute()` actually does it.
|
||||||
* **`EppToolCommand`** -- Commands that work by executing EPP commands against
|
* **`EppToolCommand`** -- Commands that work by executing EPP commands against
|
||||||
the server, usually by filling in a template with parameters that were passed
|
the server, usually by filling in a template with parameters that were
|
||||||
on the command-line.
|
passed on the command-line.
|
||||||
* **`MutatingEppToolCommand`** -- A sub-class of `EppToolCommand` that provides
|
* **`MutatingEppToolCommand`** -- A sub-class of `EppToolCommand` that
|
||||||
a `--dry_run` flag, that, if passed, will display the output from the server
|
provides a `--dry_run` flag, that, if passed, will display the output from
|
||||||
of what the command would've done without actually committing those changes.
|
the server of what the command would've done without actually committing
|
||||||
* **`GetEppResourceCommand`** -- Gets individual EPP resources from the server
|
those changes.
|
||||||
and outputs them.
|
* **`GetEppResourceCommand`** -- Gets individual EPP resources from the server
|
||||||
* **`ListObjectsCommand`** -- Lists all objects of a specific type from the
|
and outputs them.
|
||||||
server and outputs them.
|
* **`ListObjectsCommand`** -- Lists all objects of a specific type from the
|
||||||
* **`MutatingCommand`** -- Provides a facility to create or update entities in
|
server and outputs them.
|
||||||
Datastore, and uses a diff algorithm to display the changes that will be made
|
* **`MutatingCommand`** -- Provides a facility to create or update entities in
|
||||||
before committing them.
|
Datastore, and uses a diff algorithm to display the changes that will be
|
||||||
|
made before committing them.
|
||||||
|
|
|
@ -658,4 +658,22 @@ public final class ConfigModule {
|
||||||
public static Duration provideMetricsWriteInterval() {
|
public static Duration provideMetricsWriteInterval() {
|
||||||
return Duration.standardSeconds(60);
|
return Duration.standardSeconds(60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Config("contactAutomaticTransferLength")
|
||||||
|
public static Duration provideContactAutomaticTransferLength(RegistryConfig config) {
|
||||||
|
return config.getContactAutomaticTransferLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Config("asyncDeleteFlowMapreduceDelay")
|
||||||
|
public static Duration provideAsyncDeleteFlowMapreduceDelay(RegistryConfig config) {
|
||||||
|
return config.getAsyncDeleteFlowMapreduceDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Config("maxChecks")
|
||||||
|
public static int provideMaxChecks(RegistryConfig config) {
|
||||||
|
return config.getMaxChecks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,11 @@
|
||||||
<module>default</module>
|
<module>default</module>
|
||||||
<threadsafe>true</threadsafe>
|
<threadsafe>true</threadsafe>
|
||||||
<sessions-enabled>true</sessions-enabled>
|
<sessions-enabled>true</sessions-enabled>
|
||||||
<instance-class>F4_1G</instance-class>
|
<instance-class>B4_1G</instance-class>
|
||||||
<automatic-scaling>
|
<basic-scaling>
|
||||||
<min-idle-instances>0</min-idle-instances>
|
<max-instances>10</max-instances>
|
||||||
<max-idle-instances>automatic</max-idle-instances>
|
<idle-timeout>10m</idle-timeout>
|
||||||
<min-pending-latency>automatic</min-pending-latency>
|
</basic-scaling>
|
||||||
<max-pending-latency>100ms</max-pending-latency>
|
|
||||||
<max-concurrent-requests>10</max-concurrent-requests>
|
|
||||||
</automatic-scaling>
|
|
||||||
|
|
||||||
<system-properties>
|
<system-properties>
|
||||||
<property name="java.util.logging.config.file"
|
<property name="java.util.logging.config.file"
|
||||||
|
|
|
@ -7,18 +7,6 @@
|
||||||
<bucket-size>5</bucket-size>
|
<bucket-size>5</bucket-size>
|
||||||
</queue>
|
</queue>
|
||||||
|
|
||||||
<queue>
|
|
||||||
<name>dns-cron</name>
|
|
||||||
<!-- There is no point allowing more than 10/s because the pull queue that feeds
|
|
||||||
this job will refuse to service more than 10 qps. See
|
|
||||||
https://cloud.google.com/appengine/docs/java/javadoc/com/google/appengine/api/taskqueue/Queue#leaseTasks-long-java.util.concurrent.TimeUnit-long- -->
|
|
||||||
<rate>10/s</rate>
|
|
||||||
<bucket-size>100</bucket-size>
|
|
||||||
<retry-parameters>
|
|
||||||
<task-retry-limit>1</task-retry-limit>
|
|
||||||
</retry-parameters>
|
|
||||||
</queue>
|
|
||||||
|
|
||||||
<queue>
|
<queue>
|
||||||
<name>dns-pull</name>
|
<name>dns-pull</name>
|
||||||
<mode>pull</mode>
|
<mode>pull</mode>
|
||||||
|
|
|
@ -6,14 +6,11 @@
|
||||||
<module>default</module>
|
<module>default</module>
|
||||||
<threadsafe>true</threadsafe>
|
<threadsafe>true</threadsafe>
|
||||||
<sessions-enabled>true</sessions-enabled>
|
<sessions-enabled>true</sessions-enabled>
|
||||||
<instance-class>F4_1G</instance-class>
|
<instance-class>B4_1G</instance-class>
|
||||||
<automatic-scaling>
|
<basic-scaling>
|
||||||
<min-idle-instances>0</min-idle-instances>
|
<max-instances>10</max-instances>
|
||||||
<max-idle-instances>automatic</max-idle-instances>
|
<idle-timeout>10m</idle-timeout>
|
||||||
<min-pending-latency>automatic</min-pending-latency>
|
</basic-scaling>
|
||||||
<max-pending-latency>100ms</max-pending-latency>
|
|
||||||
<max-concurrent-requests>10</max-concurrent-requests>
|
|
||||||
</automatic-scaling>
|
|
||||||
|
|
||||||
<system-properties>
|
<system-properties>
|
||||||
<property name="java.util.logging.config.file"
|
<property name="java.util.logging.config.file"
|
||||||
|
|
|
@ -6,14 +6,11 @@
|
||||||
<module>default</module>
|
<module>default</module>
|
||||||
<threadsafe>true</threadsafe>
|
<threadsafe>true</threadsafe>
|
||||||
<sessions-enabled>true</sessions-enabled>
|
<sessions-enabled>true</sessions-enabled>
|
||||||
<instance-class>F4_1G</instance-class>
|
<instance-class>B4_1G</instance-class>
|
||||||
<automatic-scaling>
|
<basic-scaling>
|
||||||
<min-idle-instances>1</min-idle-instances>
|
<max-instances>10</max-instances>
|
||||||
<max-idle-instances>automatic</max-idle-instances>
|
<idle-timeout>10m</idle-timeout>
|
||||||
<min-pending-latency>automatic</min-pending-latency>
|
</basic-scaling>
|
||||||
<max-pending-latency>100ms</max-pending-latency>
|
|
||||||
<max-concurrent-requests>10</max-concurrent-requests>
|
|
||||||
</automatic-scaling>
|
|
||||||
|
|
||||||
|
|
||||||
<system-properties>
|
<system-properties>
|
||||||
|
|
|
@ -44,6 +44,7 @@ java_library(
|
||||||
"//java/google/registry/mapreduce",
|
"//java/google/registry/mapreduce",
|
||||||
"//java/google/registry/mapreduce/inputs",
|
"//java/google/registry/mapreduce/inputs",
|
||||||
"//java/google/registry/model",
|
"//java/google/registry/model",
|
||||||
|
"//java/google/registry/monitoring/metrics",
|
||||||
"//java/google/registry/monitoring/whitebox",
|
"//java/google/registry/monitoring/whitebox",
|
||||||
"//java/google/registry/pricing",
|
"//java/google/registry/pricing",
|
||||||
"//java/google/registry/request",
|
"//java/google/registry/request",
|
||||||
|
|
|
@ -14,8 +14,7 @@
|
||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.appengine.api.users.UserServiceFactory.getUserService;
|
import com.google.appengine.api.users.UserService;
|
||||||
|
|
||||||
import google.registry.request.Action;
|
import google.registry.request.Action;
|
||||||
import google.registry.request.Action.Method;
|
import google.registry.request.Action.Method;
|
||||||
import google.registry.request.Payload;
|
import google.registry.request.Payload;
|
||||||
|
@ -35,13 +34,14 @@ public class EppConsoleAction implements Runnable {
|
||||||
@Inject @Payload byte[] inputXmlBytes;
|
@Inject @Payload byte[] inputXmlBytes;
|
||||||
@Inject HttpSession session;
|
@Inject HttpSession session;
|
||||||
@Inject EppRequestHandler eppRequestHandler;
|
@Inject EppRequestHandler eppRequestHandler;
|
||||||
|
@Inject UserService userService;
|
||||||
@Inject EppConsoleAction() {}
|
@Inject EppConsoleAction() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
eppRequestHandler.executeEpp(
|
eppRequestHandler.executeEpp(
|
||||||
new HttpSessionMetadata(session),
|
new HttpSessionMetadata(session),
|
||||||
new GaeUserCredentials(getUserService().getCurrentUser()),
|
GaeUserCredentials.forCurrentUser(userService),
|
||||||
EppRequestSource.CONSOLE,
|
EppRequestSource.CONSOLE,
|
||||||
false, // This endpoint is never a dry run.
|
false, // This endpoint is never a dry run.
|
||||||
false, // This endpoint is never a superuser.
|
false, // This endpoint is never a superuser.
|
||||||
|
|
|
@ -18,6 +18,7 @@ import static google.registry.flows.EppXmlTransformer.unmarshal;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import google.registry.flows.FlowModule.EppExceptionInProviderException;
|
import google.registry.flows.FlowModule.EppExceptionInProviderException;
|
||||||
import google.registry.model.eppcommon.Trid;
|
import google.registry.model.eppcommon.Trid;
|
||||||
import google.registry.model.eppinput.EppInput;
|
import google.registry.model.eppinput.EppInput;
|
||||||
|
@ -42,7 +43,8 @@ public final class EppController {
|
||||||
|
|
||||||
@Inject Clock clock;
|
@Inject Clock clock;
|
||||||
@Inject FlowComponent.Builder flowComponentBuilder;
|
@Inject FlowComponent.Builder flowComponentBuilder;
|
||||||
@Inject EppMetric.Builder metric;
|
@Inject EppMetric.Builder metricBuilder;
|
||||||
|
@Inject EppMetrics eppMetrics;
|
||||||
@Inject BigQueryMetricsEnqueuer bigQueryMetricsEnqueuer;
|
@Inject BigQueryMetricsEnqueuer bigQueryMetricsEnqueuer;
|
||||||
@Inject EppController() {}
|
@Inject EppController() {}
|
||||||
|
|
||||||
|
@ -54,20 +56,20 @@ public final class EppController {
|
||||||
boolean isDryRun,
|
boolean isDryRun,
|
||||||
boolean isSuperuser,
|
boolean isSuperuser,
|
||||||
byte[] inputXmlBytes) {
|
byte[] inputXmlBytes) {
|
||||||
metric.setClientId(sessionMetadata.getClientId());
|
metricBuilder.setClientId(Optional.fromNullable(sessionMetadata.getClientId()));
|
||||||
metric.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL");
|
metricBuilder.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL");
|
||||||
try {
|
try {
|
||||||
EppInput eppInput;
|
EppInput eppInput;
|
||||||
try {
|
try {
|
||||||
eppInput = unmarshal(EppInput.class, inputXmlBytes);
|
eppInput = unmarshal(EppInput.class, inputXmlBytes);
|
||||||
} catch (EppException e) {
|
} catch (EppException e) {
|
||||||
// Send the client an error message, with no clTRID since we couldn't unmarshal it.
|
// Send the client an error message, with no clTRID since we couldn't unmarshal it.
|
||||||
metric.setStatus(e.getResult().getCode());
|
metricBuilder.setStatus(e.getResult().getCode());
|
||||||
return getErrorResponse(clock, e.getResult(), Trid.create(null));
|
return getErrorResponse(clock, e.getResult(), Trid.create(null));
|
||||||
}
|
}
|
||||||
metric.setCommandName(eppInput.getCommandName());
|
metricBuilder.setCommandName(eppInput.getCommandName());
|
||||||
if (!eppInput.getTargetIds().isEmpty()) {
|
if (!eppInput.getTargetIds().isEmpty()) {
|
||||||
metric.setEppTarget(Joiner.on(',').join(eppInput.getTargetIds()));
|
metricBuilder.setEppTarget(Joiner.on(',').join(eppInput.getTargetIds()));
|
||||||
}
|
}
|
||||||
EppOutput output = runFlowConvertEppErrors(flowComponentBuilder
|
EppOutput output = runFlowConvertEppErrors(flowComponentBuilder
|
||||||
.flowModule(new FlowModule.Builder()
|
.flowModule(new FlowModule.Builder()
|
||||||
|
@ -81,11 +83,14 @@ public final class EppController {
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build());
|
||||||
if (output.isResponse()) {
|
if (output.isResponse()) {
|
||||||
metric.setStatus(output.getResponse().getResult().getCode());
|
metricBuilder.setStatus(output.getResponse().getResult().getCode());
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
} finally {
|
} finally {
|
||||||
bigQueryMetricsEnqueuer.export(metric.build());
|
EppMetric metric = metricBuilder.build();
|
||||||
|
bigQueryMetricsEnqueuer.export(metric);
|
||||||
|
eppMetrics.incrementEppRequests(metric);
|
||||||
|
eppMetrics.recordProcessingTime(metric);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -250,4 +250,12 @@ public abstract class EppException extends Exception {
|
||||||
super("Specified protocol version is not implemented");
|
super("Specified protocol version is not implemented");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Command failed. */
|
||||||
|
@EppResultCode(Code.CommandFailed)
|
||||||
|
public static class CommandFailedException extends EppException {
|
||||||
|
public CommandFailedException() {
|
||||||
|
super("Command failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
72
java/google/registry/flows/EppMetrics.java
Normal file
72
java/google/registry/flows/EppMetrics.java
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import google.registry.monitoring.metrics.EventMetric;
|
||||||
|
import google.registry.monitoring.metrics.IncrementableMetric;
|
||||||
|
import google.registry.monitoring.metrics.LabelDescriptor;
|
||||||
|
import google.registry.monitoring.metrics.MetricRegistryImpl;
|
||||||
|
import google.registry.monitoring.whitebox.EppMetric;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/** EPP Instrumentation. */
|
||||||
|
public class EppMetrics {
|
||||||
|
|
||||||
|
private static final ImmutableSet<LabelDescriptor> LABEL_DESCRIPTORS =
|
||||||
|
ImmutableSet.of(
|
||||||
|
LabelDescriptor.create("command", "The name of the command."),
|
||||||
|
LabelDescriptor.create("client_id", "The name of the client."),
|
||||||
|
LabelDescriptor.create("status", "The return status of the command."));
|
||||||
|
|
||||||
|
private static final IncrementableMetric eppRequests =
|
||||||
|
MetricRegistryImpl.getDefault()
|
||||||
|
.newIncrementableMetric(
|
||||||
|
"/epp/requests", "Count of EPP Requests", "count", LABEL_DESCRIPTORS);
|
||||||
|
|
||||||
|
private static final EventMetric processingTime =
|
||||||
|
MetricRegistryImpl.getDefault()
|
||||||
|
.newEventMetric(
|
||||||
|
"/epp/processing_time",
|
||||||
|
"EPP Processing Time",
|
||||||
|
"milliseconds",
|
||||||
|
LABEL_DESCRIPTORS,
|
||||||
|
EventMetric.DEFAULT_FITTER);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public EppMetrics() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment a counter which tracks EPP requests.
|
||||||
|
*
|
||||||
|
* @see EppController
|
||||||
|
* @see FlowRunner
|
||||||
|
*/
|
||||||
|
public void incrementEppRequests(EppMetric metric) {
|
||||||
|
eppRequests.increment(
|
||||||
|
metric.getCommandName().or(""),
|
||||||
|
metric.getClientId().or(""),
|
||||||
|
metric.getStatus().isPresent() ? metric.getStatus().toString() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Record the server-side processing time for an EPP request. */
|
||||||
|
public void recordProcessingTime(EppMetric metric) {
|
||||||
|
processingTime.record(
|
||||||
|
metric.getEndTimestamp().getMillis() - metric.getStartTimestamp().getMillis(),
|
||||||
|
metric.getCommandName().or(""),
|
||||||
|
metric.getClientId().or(""),
|
||||||
|
metric.getStatus().isPresent() ? metric.getStatus().toString() : "");
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,13 +16,21 @@ package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
|
||||||
import google.registry.flows.picker.FlowPicker;
|
import google.registry.flows.picker.FlowPicker;
|
||||||
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
import google.registry.model.eppcommon.Trid;
|
import google.registry.model.eppcommon.Trid;
|
||||||
import google.registry.model.eppinput.EppInput;
|
import google.registry.model.eppinput.EppInput;
|
||||||
|
import google.registry.model.eppinput.EppInput.ResourceCommandWrapper;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||||
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
|
|
||||||
/** Module to choose and instantiate an EPP flow. */
|
/** Module to choose and instantiate an EPP flow. */
|
||||||
|
@ -142,10 +150,11 @@ public class FlowModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FlowScope
|
@FlowScope
|
||||||
@Nullable
|
|
||||||
@ClientId
|
@ClientId
|
||||||
static String provideClientId(SessionMetadata sessionMetadata) {
|
static String provideClientId(SessionMetadata sessionMetadata) {
|
||||||
return sessionMetadata.getClientId();
|
// Treat a missing clientId as null so we can always inject a non-null value. All we do with the
|
||||||
|
// clientId is log it (as "") or detect its absence, both of which work fine with empty.
|
||||||
|
return Strings.nullToEmpty(sessionMetadata.getClientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -164,6 +173,50 @@ public class FlowModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FlowScope
|
||||||
|
static ResourceCommand provideResourceCommand(EppInput eppInput) {
|
||||||
|
return ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
|
||||||
|
.getResourceCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FlowScope
|
||||||
|
static Optional<AuthInfo> provideAuthInfo(ResourceCommand resourceCommand) {
|
||||||
|
return Optional.fromNullable(((SingleResourceCommand) resourceCommand).getAuthInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a partially filled in {@link HistoryEntry} builder.
|
||||||
|
*
|
||||||
|
* <p>This is not marked with {@link FlowScope} so that each retry gets a fresh one. Otherwise,
|
||||||
|
* the fact that the builder is one-use would cause NPEs.
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
static HistoryEntry.Builder provideHistoryEntryBuilder(
|
||||||
|
Trid trid,
|
||||||
|
@InputXml byte[] inputXmlBytes,
|
||||||
|
@Superuser boolean isSuperuser,
|
||||||
|
@ClientId String clientId,
|
||||||
|
EppRequestSource eppRequestSource,
|
||||||
|
EppInput eppInput) {
|
||||||
|
HistoryEntry.Builder historyBuilder = new HistoryEntry.Builder()
|
||||||
|
.setTrid(trid)
|
||||||
|
.setXmlBytes(inputXmlBytes)
|
||||||
|
.setBySuperuser(isSuperuser)
|
||||||
|
.setClientId(clientId);
|
||||||
|
MetadataExtension metadataExtension = eppInput.getSingleExtension(MetadataExtension.class);
|
||||||
|
if (metadataExtension != null) {
|
||||||
|
if (!eppRequestSource.equals(EppRequestSource.TOOL)) {
|
||||||
|
throw new EppExceptionInProviderException(new OnlyToolCanPassMetadataException());
|
||||||
|
}
|
||||||
|
historyBuilder
|
||||||
|
.setReason(metadataExtension.getReason())
|
||||||
|
.setRequestedByRegistrar(metadataExtension.getRequestedByRegistrar());
|
||||||
|
}
|
||||||
|
return historyBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
/** Wrapper class to carry an {@link EppException} to the calling code. */
|
/** Wrapper class to carry an {@link EppException} to the calling code. */
|
||||||
static class EppExceptionInProviderException extends RuntimeException {
|
static class EppExceptionInProviderException extends RuntimeException {
|
||||||
EppExceptionInProviderException(EppException exception) {
|
EppExceptionInProviderException(EppException exception) {
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.base.Strings.nullToEmpty;
|
|
||||||
import static com.google.common.base.Throwables.getStackTraceAsString;
|
import static com.google.common.base.Throwables.getStackTraceAsString;
|
||||||
import static com.google.common.io.BaseEncoding.base64;
|
import static com.google.common.io.BaseEncoding.base64;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
@ -34,7 +33,6 @@ import google.registry.model.eppoutput.EppOutput;
|
||||||
import google.registry.monitoring.whitebox.EppMetric;
|
import google.registry.monitoring.whitebox.EppMetric;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import google.registry.util.FormattingLogger;
|
import google.registry.util.FormattingLogger;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -57,7 +55,7 @@ public class FlowRunner {
|
||||||
|
|
||||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||||
|
|
||||||
@Inject @Nullable @ClientId String clientId;
|
@Inject @ClientId String clientId;
|
||||||
@Inject Clock clock;
|
@Inject Clock clock;
|
||||||
@Inject TransportCredentials credentials;
|
@Inject TransportCredentials credentials;
|
||||||
@Inject EppInput eppInput;
|
@Inject EppInput eppInput;
|
||||||
|
@ -96,7 +94,7 @@ public class FlowRunner {
|
||||||
REPORTING_LOG_SIGNATURE,
|
REPORTING_LOG_SIGNATURE,
|
||||||
JSONValue.toJSONString(ImmutableMap.<String, Object>of(
|
JSONValue.toJSONString(ImmutableMap.<String, Object>of(
|
||||||
"trid", trid.getServerTransactionId(),
|
"trid", trid.getServerTransactionId(),
|
||||||
"clientId", nullToEmpty(clientId),
|
"clientId", clientId,
|
||||||
"xml", prettyXml,
|
"xml", prettyXml,
|
||||||
"xmlBytes", xmlBase64)));
|
"xmlBytes", xmlBase64)));
|
||||||
if (!isTransactional) {
|
if (!isTransactional) {
|
||||||
|
|
|
@ -14,11 +14,12 @@
|
||||||
|
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.appengine.api.users.UserServiceFactory.getUserService;
|
|
||||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||||
import static com.google.common.base.Strings.nullToEmpty;
|
import static com.google.common.base.Strings.nullToEmpty;
|
||||||
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
import com.google.appengine.api.users.User;
|
import com.google.appengine.api.users.User;
|
||||||
|
import com.google.appengine.api.users.UserService;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import google.registry.flows.EppException.AuthenticationErrorException;
|
import google.registry.flows.EppException.AuthenticationErrorException;
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
|
@ -28,11 +29,41 @@ import javax.annotation.Nullable;
|
||||||
/** Credentials provided by {@link com.google.appengine.api.users.UserService}. */
|
/** Credentials provided by {@link com.google.appengine.api.users.UserService}. */
|
||||||
public class GaeUserCredentials implements TransportCredentials {
|
public class GaeUserCredentials implements TransportCredentials {
|
||||||
|
|
||||||
final User gaeUser;
|
private final User gaeUser;
|
||||||
|
private final Boolean isAdmin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance for the current user, as determined by {@code UserService}.
|
||||||
|
*
|
||||||
|
* <p>Note that the current user may be null (i.e. there is no logged in user).
|
||||||
|
*/
|
||||||
|
public static GaeUserCredentials forCurrentUser(UserService userService) {
|
||||||
|
User user = userService.getCurrentUser();
|
||||||
|
return new GaeUserCredentials(user, user != null ? userService.isUserAdmin() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create an instance that represents an explicit user (for testing purposes). */
|
||||||
|
@VisibleForTesting
|
||||||
|
public static GaeUserCredentials forTestingUser(User gaeUser, Boolean isAdmin) {
|
||||||
|
checkArgumentNotNull(gaeUser);
|
||||||
|
checkArgumentNotNull(isAdmin);
|
||||||
|
return new GaeUserCredentials(gaeUser, isAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create an instance that represents a non-logged in user (for testing purposes). */
|
||||||
|
@VisibleForTesting
|
||||||
|
public static GaeUserCredentials forLoggedOutUser() {
|
||||||
|
return new GaeUserCredentials(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GaeUserCredentials(@Nullable User gaeUser, @Nullable Boolean isAdmin) {
|
||||||
|
this.gaeUser = gaeUser;
|
||||||
|
this.isAdmin = isAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public GaeUserCredentials(@Nullable User gaeUser) {
|
User getUser() {
|
||||||
this.gaeUser = gaeUser;
|
return gaeUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -42,7 +73,7 @@ public class GaeUserCredentials implements TransportCredentials {
|
||||||
throw new UserNotLoggedInException();
|
throw new UserNotLoggedInException();
|
||||||
}
|
}
|
||||||
// Allow admins to act as any registrar.
|
// Allow admins to act as any registrar.
|
||||||
if (getUserService().isUserAdmin()) {
|
if (Boolean.TRUE.equals(isAdmin)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Check Registrar's contacts to see if any are associated with this gaeUserId.
|
// Check Registrar's contacts to see if any are associated with this gaeUserId.
|
||||||
|
@ -59,6 +90,7 @@ public class GaeUserCredentials implements TransportCredentials {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return toStringHelper(getClass())
|
return toStringHelper(getClass())
|
||||||
.add("gaeUser", gaeUser)
|
.add("gaeUser", gaeUser)
|
||||||
|
.add("isAdmin", isAdmin)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,10 @@ public abstract class LoggedInFlow extends Flow {
|
||||||
allowedTlds = registrar.getAllowedTlds();
|
allowedTlds = registrar.getAllowedTlds();
|
||||||
}
|
}
|
||||||
initLoggedInFlow();
|
initLoggedInFlow();
|
||||||
if (!difference(extensionClasses, getValidRequestExtensions()).isEmpty()) {
|
Set<Class<? extends CommandExtension>> unimplementedExtensions =
|
||||||
|
difference(extensionClasses, getValidRequestExtensions());
|
||||||
|
if (!unimplementedExtensions.isEmpty()) {
|
||||||
|
logger.infofmt("Unimplemented extensions: %s", unimplementedExtensions);
|
||||||
throw new UnimplementedExtensionException();
|
throw new UnimplementedExtensionException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,29 @@
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static google.registry.model.EppResourceUtils.queryDomainsUsingResource;
|
||||||
import static google.registry.model.domain.DomainResource.extendRegistrationWithCap;
|
import static google.registry.model.domain.DomainResource.extendRegistrationWithCap;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import com.googlecode.objectify.Work;
|
||||||
import google.registry.flows.EppException.AuthorizationErrorException;
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||||
import google.registry.flows.EppException.InvalidAuthorizationInformationErrorException;
|
import google.registry.flows.EppException.InvalidAuthorizationInformationErrorException;
|
||||||
|
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToDeleteIsReferencedException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
import google.registry.model.EppResource.Builder;
|
import google.registry.model.EppResource.Builder;
|
||||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
import google.registry.model.eppcommon.AuthInfo;
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
import google.registry.model.eppcommon.AuthInfo.BadAuthInfoException;
|
import google.registry.model.eppcommon.AuthInfo.BadAuthInfoException;
|
||||||
|
@ -43,6 +54,8 @@ import google.registry.model.transfer.TransferResponse;
|
||||||
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||||
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||||
import google.registry.model.transfer.TransferStatus;
|
import google.registry.model.transfer.TransferStatus;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/** Static utility functions for resource transfer flows. */
|
/** Static utility functions for resource transfer flows. */
|
||||||
|
@ -52,6 +65,9 @@ public class ResourceFlowUtils {
|
||||||
private static final ImmutableSet<TransferStatus> ADD_EXDATE_STATUSES = Sets.immutableEnumSet(
|
private static final ImmutableSet<TransferStatus> ADD_EXDATE_STATUSES = Sets.immutableEnumSet(
|
||||||
TransferStatus.PENDING, TransferStatus.CLIENT_APPROVED, TransferStatus.SERVER_APPROVED);
|
TransferStatus.PENDING, TransferStatus.CLIENT_APPROVED, TransferStatus.SERVER_APPROVED);
|
||||||
|
|
||||||
|
/** In {@link #failfastForAsyncDelete}, check this (arbitrary) number of query results. */
|
||||||
|
private static final int FAILFAST_CHECK_COUNT = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a transfer response using the id and type of this resource and the specified
|
* Create a transfer response using the id and type of this resource and the specified
|
||||||
* {@link TransferData}.
|
* {@link TransferData}.
|
||||||
|
@ -166,6 +182,41 @@ public class ResourceFlowUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check whether an asynchronous delete would obviously fail, and throw an exception if so. */
|
||||||
|
public static <R extends EppResource> void failfastForAsyncDelete(
|
||||||
|
final String targetId,
|
||||||
|
final DateTime now,
|
||||||
|
final Class<R> resourceClass,
|
||||||
|
final Function<DomainBase, ImmutableSet<?>> getPotentialReferences) throws EppException {
|
||||||
|
// Enter a transactionless context briefly.
|
||||||
|
EppException failfastException = ofy().doTransactionless(new Work<EppException>() {
|
||||||
|
@Override
|
||||||
|
public EppException run() {
|
||||||
|
final ForeignKeyIndex<R> fki = ForeignKeyIndex.load(resourceClass, targetId, now);
|
||||||
|
if (fki == null) {
|
||||||
|
return new ResourceToMutateDoesNotExistException(resourceClass, targetId);
|
||||||
|
}
|
||||||
|
// Query for the first few linked domains, and if found, actually load them. The query is
|
||||||
|
// eventually consistent and so might be very stale, but the direct load will not be stale,
|
||||||
|
// just non-transactional. If we find at least one actual reference then we can reliably
|
||||||
|
// fail. If we don't find any, we can't trust the query and need to do the full mapreduce.
|
||||||
|
List<Key<DomainBase>> keys = queryDomainsUsingResource(
|
||||||
|
resourceClass, fki.getResourceKey(), now, FAILFAST_CHECK_COUNT);
|
||||||
|
Predicate<DomainBase> predicate = new Predicate<DomainBase>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(DomainBase domain) {
|
||||||
|
return getPotentialReferences.apply(domain).contains(fki.getResourceKey());
|
||||||
|
}};
|
||||||
|
return Iterables.any(ofy().load().keys(keys).values(), predicate)
|
||||||
|
? new ResourceToDeleteIsReferencedException()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (failfastException != null) {
|
||||||
|
throw failfastException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The specified resource belongs to another client. */
|
/** The specified resource belongs to another client. */
|
||||||
public static class ResourceNotOwnedException extends AuthorizationErrorException {
|
public static class ResourceNotOwnedException extends AuthorizationErrorException {
|
||||||
public ResourceNotOwnedException() {
|
public ResourceNotOwnedException() {
|
||||||
|
@ -173,6 +224,14 @@ public class ResourceFlowUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check that the given AuthInfo is either missing or else is valid for the given resource. */
|
||||||
|
public static void verifyOptionalAuthInfoForResource(
|
||||||
|
Optional<AuthInfo> authInfo, EppResource resource) throws EppException {
|
||||||
|
if (authInfo.isPresent()) {
|
||||||
|
verifyAuthInfoForResource(authInfo.get(), resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Check that the given AuthInfo is valid for the given resource. */
|
/** Check that the given AuthInfo is valid for the given resource. */
|
||||||
public static void verifyAuthInfoForResource(AuthInfo authInfo, EppResource resource)
|
public static void verifyAuthInfoForResource(AuthInfo authInfo, EppResource resource)
|
||||||
throws EppException {
|
throws EppException {
|
||||||
|
@ -183,6 +242,15 @@ public class ResourceFlowUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Check that the resource does not have any disallowed status values. */
|
||||||
|
public static void verifyNoDisallowedStatuses(
|
||||||
|
EppResource resource, ImmutableSet<StatusValue> disallowedStatuses) throws EppException {
|
||||||
|
Set<StatusValue> problems = Sets.intersection(resource.getStatusValues(), disallowedStatuses);
|
||||||
|
if (!problems.isEmpty()) {
|
||||||
|
throw new ResourceStatusProhibitsOperationException(problems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Authorization information for accessing resource is invalid. */
|
/** Authorization information for accessing resource is invalid. */
|
||||||
public static class BadAuthInfoForResourceException
|
public static class BadAuthInfoForResourceException
|
||||||
extends InvalidAuthorizationInformationErrorException {
|
extends InvalidAuthorizationInformationErrorException {
|
||||||
|
|
|
@ -36,7 +36,7 @@ public abstract class ResourceSyncDeleteFlow
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected final R createOrMutateResource() {
|
protected final R createOrMutateResource() throws EppException {
|
||||||
B builder = (B) prepareDeletedResourceAsBuilder(existingResource, now);
|
B builder = (B) prepareDeletedResourceAsBuilder(existingResource, now);
|
||||||
setDeleteProperties(builder);
|
setDeleteProperties(builder);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
@ -52,7 +52,7 @@ public abstract class ResourceSyncDeleteFlow
|
||||||
|
|
||||||
/** Set any resource-specific properties before deleting. */
|
/** Set any resource-specific properties before deleting. */
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
protected void setDeleteProperties(B builder) {}
|
protected void setDeleteProperties(B builder) throws EppException {}
|
||||||
|
|
||||||
/** Modify any other resources that need to be informed of this delete. */
|
/** Modify any other resources that need to be informed of this delete. */
|
||||||
protected void modifySyncDeleteRelatedResources() {}
|
protected void modifySyncDeleteRelatedResources() {}
|
||||||
|
|
|
@ -77,7 +77,7 @@ import org.joda.time.Duration;
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void initResourceCreateOrMutateFlow() {
|
protected final void initResourceCreateOrMutateFlow() throws EppException {
|
||||||
initResourceTransferRequestFlow();
|
initResourceTransferRequestFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,8 @@ import org.joda.time.Duration;
|
||||||
verifyTransferRequestIsAllowed();
|
verifyTransferRequestIsAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransferData.Builder createTransferDataBuilder(TransferStatus transferStatus) {
|
private TransferData.Builder
|
||||||
|
createTransferDataBuilder(TransferStatus transferStatus) throws EppException {
|
||||||
TransferData.Builder builder = new TransferData.Builder()
|
TransferData.Builder builder = new TransferData.Builder()
|
||||||
.setGainingClientId(gainingClient.getId())
|
.setGainingClientId(gainingClient.getId())
|
||||||
.setTransferRequestTime(now)
|
.setTransferRequestTime(now)
|
||||||
|
@ -113,7 +114,7 @@ import org.joda.time.Duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PollMessage createPollMessage(
|
private PollMessage createPollMessage(
|
||||||
Client client, TransferStatus transferStatus, DateTime eventTime) {
|
Client client, TransferStatus transferStatus, DateTime eventTime) throws EppException {
|
||||||
ImmutableList.Builder<ResponseData> responseData = new ImmutableList.Builder<>();
|
ImmutableList.Builder<ResponseData> responseData = new ImmutableList.Builder<>();
|
||||||
responseData.add(createTransferResponse(
|
responseData.add(createTransferResponse(
|
||||||
existingResource, createTransferDataBuilder(transferStatus).build(), now));
|
existingResource, createTransferDataBuilder(transferStatus).build(), now));
|
||||||
|
@ -132,7 +133,7 @@ import org.joda.time.Duration;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected final R createOrMutateResource() {
|
protected final R createOrMutateResource() throws EppException {
|
||||||
// Figure out transfer expiration time once we've verified that the existingResource does in
|
// Figure out transfer expiration time once we've verified that the existingResource does in
|
||||||
// fact exist (otherwise we won't know which TLD to get this figure off of).
|
// fact exist (otherwise we won't know which TLD to get this figure off of).
|
||||||
transferExpirationTime = now.plus(getAutomaticTransferLength());
|
transferExpirationTime = now.plus(getAutomaticTransferLength());
|
||||||
|
@ -158,7 +159,7 @@ import org.joda.time.Duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Subclasses can override this to do further initialization. */
|
/** Subclasses can override this to do further initialization. */
|
||||||
protected void initResourceTransferRequestFlow() {}
|
protected void initResourceTransferRequestFlow() throws EppException {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses can override this to return the keys of any entities that need to be deleted if the
|
* Subclasses can override this to return the keys of any entities that need to be deleted if the
|
||||||
|
@ -173,8 +174,8 @@ import org.joda.time.Duration;
|
||||||
protected void verifyTransferRequestIsAllowed() throws EppException {}
|
protected void verifyTransferRequestIsAllowed() throws EppException {}
|
||||||
|
|
||||||
/** Subclasses can override this to modify fields on the transfer data builder. */
|
/** Subclasses can override this to modify fields on the transfer data builder. */
|
||||||
protected void setTransferDataProperties(
|
@SuppressWarnings("unused")
|
||||||
@SuppressWarnings("unused") TransferData.Builder builder) {}
|
protected void setTransferDataProperties(TransferData.Builder builder) throws EppException {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final EppOutput getOutput() throws EppException {
|
protected final EppOutput getOutput() throws EppException {
|
||||||
|
|
|
@ -15,34 +15,46 @@
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import google.registry.flows.ResourceCheckFlow;
|
import google.registry.config.ConfigModule.Config;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.exceptions.TooManyResourceChecksException;
|
||||||
import google.registry.model.contact.ContactCommand.Check;
|
import google.registry.model.contact.ContactCommand.Check;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.eppoutput.CheckData;
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
import google.registry.model.eppoutput.CheckData.ContactCheck;
|
import google.registry.model.eppoutput.CheckData.ContactCheck;
|
||||||
import google.registry.model.eppoutput.CheckData.ContactCheckData;
|
import google.registry.model.eppoutput.CheckData.ContactCheckData;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that checks whether a contact can be provisioned.
|
* An EPP flow that checks whether a contact can be provisioned.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceCheckFlow.TooManyResourceChecksException}
|
* @error {@link google.registry.flows.exceptions.TooManyResourceChecksException}
|
||||||
*/
|
*/
|
||||||
public class ContactCheckFlow extends ResourceCheckFlow<ContactResource, Check> {
|
public class ContactCheckFlow extends LoggedInFlow {
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject @Config("maxChecks") int maxChecks;
|
||||||
@Inject ContactCheckFlow() {}
|
@Inject ContactCheckFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CheckData getCheckData() {
|
public final EppOutput run() throws EppException {
|
||||||
Set<String> existingIds = checkResourcesExist(resourceClass, targetIds, now);
|
List<String> targetIds = ((Check) resourceCommand).getTargetIds();
|
||||||
|
if (targetIds.size() > maxChecks) {
|
||||||
|
throw new TooManyResourceChecksException(maxChecks);
|
||||||
|
}
|
||||||
|
Set<String> existingIds = checkResourcesExist(ContactResource.class, targetIds, now);
|
||||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||||
for (String id : targetIds) {
|
for (String id : targetIds) {
|
||||||
boolean unused = !existingIds.contains(id);
|
boolean unused = !existingIds.contains(id);
|
||||||
checks.add(ContactCheck.create(unused, id, unused ? null : "In use"));
|
checks.add(ContactCheck.create(unused, id, unused ? null : "In use"));
|
||||||
}
|
}
|
||||||
return ContactCheckData.create(checks.build());
|
return createOutput(Success, ContactCheckData.create(checks.build()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,25 @@ package google.registry.flows.contact;
|
||||||
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||||
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||||
import static google.registry.model.EppResourceUtils.createContactHostRoid;
|
import static google.registry.model.EppResourceUtils.createContactHostRoid;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
import static google.registry.model.eppoutput.Result.Code.Success;
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.flows.ResourceCreateFlow;
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
|
import google.registry.flows.exceptions.ResourceAlreadyExistsException;
|
||||||
import google.registry.model.contact.ContactCommand.Create;
|
import google.registry.model.contact.ContactCommand.Create;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.ContactResource.Builder;
|
import google.registry.model.contact.ContactResource.Builder;
|
||||||
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
import google.registry.model.eppoutput.CreateData.ContactCreateData;
|
import google.registry.model.eppoutput.CreateData.ContactCreateData;
|
||||||
import google.registry.model.eppoutput.EppOutput;
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
|
import google.registry.model.index.EppResourceIndex;
|
||||||
|
import google.registry.model.index.ForeignKeyIndex;
|
||||||
import google.registry.model.ofy.ObjectifyService;
|
import google.registry.model.ofy.ObjectifyService;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -33,37 +43,47 @@ import javax.inject.Inject;
|
||||||
/**
|
/**
|
||||||
* An EPP flow that creates a new contact resource.
|
* An EPP flow that creates a new contact resource.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException}
|
* @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException}
|
||||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||||
*/
|
*/
|
||||||
public class ContactCreateFlow extends ResourceCreateFlow<ContactResource, Builder, Create> {
|
public class ContactCreateFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactCreateFlow() {}
|
@Inject ContactCreateFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected EppOutput getOutput() {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
return createOutput(Success, ContactCreateData.create(newResource.getContactId(), now));
|
registerExtensions(MetadataExtension.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String createFlowRepoId() {
|
protected final EppOutput run() throws EppException {
|
||||||
return createContactHostRoid(ObjectifyService.allocateId());
|
Create command = (Create) resourceCommand;
|
||||||
}
|
if (loadByUniqueId(ContactResource.class, command.getTargetId(), now) != null) {
|
||||||
|
throw new ResourceAlreadyExistsException(command.getTargetId());
|
||||||
@Override
|
}
|
||||||
protected void verifyNewStateIsAllowed() throws EppException {
|
Builder builder = new Builder();
|
||||||
|
command.applyTo(builder);
|
||||||
|
ContactResource newResource = builder
|
||||||
|
.setCreationClientId(clientId)
|
||||||
|
.setCurrentSponsorClientId(clientId)
|
||||||
|
.setRepoId(createContactHostRoid(ObjectifyService.allocateId()))
|
||||||
|
.build();
|
||||||
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
|
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
|
||||||
validateContactAgainstPolicy(newResource);
|
validateContactAgainstPolicy(newResource);
|
||||||
}
|
historyBuilder
|
||||||
|
.setType(HistoryEntry.Type.CONTACT_CREATE)
|
||||||
@Override
|
.setModificationTime(now)
|
||||||
protected boolean storeXmlInHistoryEntry() {
|
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||||
return false;
|
.setParent(Key.create(newResource));
|
||||||
}
|
ofy().save().entities(
|
||||||
|
newResource,
|
||||||
@Override
|
historyBuilder.build(),
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
ForeignKeyIndex.create(newResource, newResource.getDeletionTime()),
|
||||||
return HistoryEntry.Type.CONTACT_CREATE;
|
EppResourceIndex.create(Key.create(newResource)));
|
||||||
|
return createOutput(Success, ContactCreateData.create(newResource.getContactId(), now));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,75 +14,107 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import static google.registry.model.EppResourceUtils.queryDomainsUsingResource;
|
import static google.registry.flows.ResourceFlowUtils.failfastForAsyncDelete;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.ConfigModule.Config;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.flows.ResourceAsyncDeleteFlow;
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
import google.registry.flows.async.AsyncFlowUtils;
|
import google.registry.flows.async.AsyncFlowUtils;
|
||||||
import google.registry.flows.async.DeleteContactResourceAction;
|
import google.registry.flows.async.DeleteContactResourceAction;
|
||||||
import google.registry.flows.async.DeleteEppResourceAction;
|
import google.registry.flows.async.DeleteEppResourceAction;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Delete;
|
import google.registry.model.contact.ContactCommand.Delete;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.ContactResource.Builder;
|
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that deletes a contact resource.
|
* An EPP flow that deletes a contact resource.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException}
|
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||||
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||||
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
* @error {@link google.registry.flows.exceptions.ResourceToDeleteIsReferencedException}
|
||||||
|
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
|
||||||
*/
|
*/
|
||||||
public class ContactDeleteFlow extends ResourceAsyncDeleteFlow<ContactResource, Builder, Delete> {
|
public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
|
|
||||||
/** In {@link #isLinkedForFailfast}, check this (arbitrary) number of resources from the query. */
|
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||||
private static final int FAILFAST_CHECK_COUNT = 5;
|
StatusValue.LINKED,
|
||||||
|
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||||
|
StatusValue.PENDING_DELETE,
|
||||||
|
StatusValue.SERVER_DELETE_PROHIBITED);
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject @Config("asyncDeleteFlowMapreduceDelay") Duration mapreduceDelay;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactDeleteFlow() {}
|
@Inject ContactDeleteFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isLinkedForFailfast(final Key<ContactResource> key) {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
// Query for the first few linked domains, and if found, actually load them. The query is
|
registerExtensions(MetadataExtension.class);
|
||||||
// eventually consistent and so might be very stale, but the direct load will not be stale,
|
|
||||||
// just non-transactional. If we find at least one actual reference then we can reliably
|
|
||||||
// fail. If we don't find any, we can't trust the query and need to do the full mapreduce.
|
|
||||||
return Iterables.any(
|
|
||||||
ofy().load().keys(
|
|
||||||
queryDomainsUsingResource(
|
|
||||||
ContactResource.class, key, now, FAILFAST_CHECK_COUNT)).values(),
|
|
||||||
new Predicate<DomainBase>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(DomainBase domain) {
|
|
||||||
return domain.getReferencedContacts().contains(key);
|
|
||||||
}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Enqueues a contact resource deletion on the mapreduce queue. */
|
|
||||||
@Override
|
@Override
|
||||||
protected final void enqueueTasks() throws EppException {
|
public final EppOutput run() throws EppException {
|
||||||
|
Delete command = (Delete) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
failfastForAsyncDelete(
|
||||||
|
targetId,
|
||||||
|
now,
|
||||||
|
ContactResource.class,
|
||||||
|
new Function<DomainBase, ImmutableSet<?>>() {
|
||||||
|
@Override
|
||||||
|
public ImmutableSet<?> apply(DomainBase domain) {
|
||||||
|
return domain.getReferencedContacts();
|
||||||
|
}});
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
|
||||||
|
verifyOptionalAuthInfoForResource(authInfo, existingResource);
|
||||||
|
if (!isSuperuser) {
|
||||||
|
verifyResourceOwnership(clientId, existingResource);
|
||||||
|
}
|
||||||
AsyncFlowUtils.enqueueMapreduceAction(
|
AsyncFlowUtils.enqueueMapreduceAction(
|
||||||
DeleteContactResourceAction.class,
|
DeleteContactResourceAction.class,
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
DeleteEppResourceAction.PARAM_RESOURCE_KEY,
|
DeleteEppResourceAction.PARAM_RESOURCE_KEY,
|
||||||
Key.create(existingResource).getString(),
|
Key.create(existingResource).getString(),
|
||||||
DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID,
|
DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID,
|
||||||
getClientId(),
|
clientId,
|
||||||
DeleteEppResourceAction.PARAM_IS_SUPERUSER,
|
DeleteEppResourceAction.PARAM_IS_SUPERUSER,
|
||||||
Boolean.toString(isSuperuser)),
|
Boolean.toString(isSuperuser)),
|
||||||
RegistryEnvironment.get().config().getAsyncDeleteFlowMapreduceDelay());
|
mapreduceDelay);
|
||||||
}
|
ContactResource newResource =
|
||||||
|
existingResource.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build();
|
||||||
@Override
|
historyBuilder
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
.setType(HistoryEntry.Type.CONTACT_PENDING_DELETE)
|
||||||
return HistoryEntry.Type.CONTACT_PENDING_DELETE;
|
.setModificationTime(now)
|
||||||
|
.setParent(Key.create(existingResource));
|
||||||
|
ofy().save().<Object>entities(newResource, historyBuilder.build());
|
||||||
|
return createOutput(SuccessWithActionPending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import static google.registry.model.contact.PostalInfo.Type.INTERNATIONALIZED;
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
@ -25,6 +26,11 @@ import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||||
import google.registry.model.contact.ContactAddress;
|
import google.registry.model.contact.ContactAddress;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.PostalInfo;
|
import google.registry.model.contact.PostalInfo;
|
||||||
|
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||||
|
import google.registry.model.poll.PollMessage;
|
||||||
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
|
import google.registry.model.transfer.TransferData;
|
||||||
|
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -58,6 +64,49 @@ public class ContactFlowUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create a poll message for the gaining client in a transfer. */
|
||||||
|
static PollMessage createGainingTransferPollMessage(
|
||||||
|
String targetId, TransferData transferData, HistoryEntry historyEntry) {
|
||||||
|
return new PollMessage.OneTime.Builder()
|
||||||
|
.setClientId(transferData.getGainingClientId())
|
||||||
|
.setEventTime(transferData.getPendingTransferExpirationTime())
|
||||||
|
.setMsg(transferData.getTransferStatus().getMessage())
|
||||||
|
.setResponseData(ImmutableList.of(
|
||||||
|
createTransferResponse(targetId, transferData),
|
||||||
|
ContactPendingActionNotificationResponse.create(
|
||||||
|
targetId,
|
||||||
|
transferData.getTransferStatus().isApproved(),
|
||||||
|
transferData.getTransferRequestTrid(),
|
||||||
|
historyEntry.getModificationTime())))
|
||||||
|
.setParent(historyEntry)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a poll message for the losing client in a transfer. */
|
||||||
|
static PollMessage createLosingTransferPollMessage(
|
||||||
|
String targetId, TransferData transferData, HistoryEntry historyEntry) {
|
||||||
|
return new PollMessage.OneTime.Builder()
|
||||||
|
.setClientId(transferData.getLosingClientId())
|
||||||
|
.setEventTime(transferData.getPendingTransferExpirationTime())
|
||||||
|
.setMsg(transferData.getTransferStatus().getMessage())
|
||||||
|
.setResponseData(ImmutableList.of(createTransferResponse(targetId, transferData)))
|
||||||
|
.setParent(historyEntry)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a {@link ContactTransferResponse} off of the info in a {@link TransferData}. */
|
||||||
|
static ContactTransferResponse createTransferResponse(
|
||||||
|
String targetId, TransferData transferData) {
|
||||||
|
return new ContactTransferResponse.Builder()
|
||||||
|
.setContactId(targetId)
|
||||||
|
.setGainingClientId(transferData.getGainingClientId())
|
||||||
|
.setLosingClientId(transferData.getLosingClientId())
|
||||||
|
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
|
||||||
|
.setTransferRequestTime(transferData.getTransferRequestTime())
|
||||||
|
.setTransferStatus(transferData.getTransferStatus())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/** Declining contact disclosure is disallowed by server policy. */
|
/** Declining contact disclosure is disallowed by server policy. */
|
||||||
static class DeclineContactDisclosureFieldDisallowedPolicyException
|
static class DeclineContactDisclosureFieldDisallowedPolicyException
|
||||||
extends ParameterValuePolicyErrorException {
|
extends ParameterValuePolicyErrorException {
|
||||||
|
|
|
@ -14,17 +14,42 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import google.registry.flows.ResourceInfoFlow;
|
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
|
||||||
|
import static google.registry.model.EppResourceUtils.cloneResourceWithLinkedStatus;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Info;
|
import google.registry.model.contact.ContactCommand.Info;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that reads a contact.
|
* An EPP flow that reads a contact.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.ResourceToQueryDoesNotExistException}
|
||||||
*/
|
*/
|
||||||
public class ContactInfoFlow extends ResourceInfoFlow<ContactResource, Info> {
|
public class ContactInfoFlow extends LoggedInFlow {
|
||||||
@Inject ContactInfoFlow() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject ContactInfoFlow() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final EppOutput run() throws EppException {
|
||||||
|
Info command = (Info) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToQueryDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
verifyOptionalAuthInfoForResource(authInfo, existingResource);
|
||||||
|
return createOutput(Success, cloneResourceWithLinkedStatus(existingResource, now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,11 +14,31 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import google.registry.flows.ResourceTransferApproveFlow;
|
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
|
import google.registry.flows.exceptions.NotPendingTransferException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Transfer;
|
import google.registry.model.contact.ContactCommand.Transfer;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.ContactResource.Builder;
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
|
import google.registry.model.poll.PollMessage;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
|
import google.registry.model.transfer.TransferStatus;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,16 +46,52 @@ import javax.inject.Inject;
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||||
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||||
* @error {@link google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
|
||||||
*/
|
*/
|
||||||
public class ContactTransferApproveFlow
|
public class ContactTransferApproveFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
extends ResourceTransferApproveFlow<ContactResource, Builder, Transfer> {
|
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactTransferApproveFlow() {}
|
@Inject ContactTransferApproveFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
return HistoryEntry.Type.CONTACT_TRANSFER_APPROVE;
|
registerExtensions(MetadataExtension.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final EppOutput run() throws EppException {
|
||||||
|
Transfer command = (Transfer) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
verifyOptionalAuthInfoForResource(authInfo, existingResource);
|
||||||
|
if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
|
||||||
|
throw new NotPendingTransferException(targetId);
|
||||||
|
}
|
||||||
|
verifyResourceOwnership(clientId, existingResource);
|
||||||
|
ContactResource newResource = existingResource.asBuilder()
|
||||||
|
.clearPendingTransfer(TransferStatus.CLIENT_APPROVED, now)
|
||||||
|
.setLastTransferTime(now)
|
||||||
|
.setCurrentSponsorClientId(existingResource.getTransferData().getGainingClientId())
|
||||||
|
.build();
|
||||||
|
HistoryEntry historyEntry = historyBuilder
|
||||||
|
.setType(HistoryEntry.Type.CONTACT_TRANSFER_APPROVE)
|
||||||
|
.setModificationTime(now)
|
||||||
|
.setParent(Key.create(existingResource))
|
||||||
|
.build();
|
||||||
|
// Create a poll message for the gaining client.
|
||||||
|
PollMessage gainingPollMessage =
|
||||||
|
createGainingTransferPollMessage(targetId, newResource.getTransferData(), historyEntry);
|
||||||
|
ofy().save().<Object>entities(newResource, historyEntry, gainingPollMessage);
|
||||||
|
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||||
|
// been implicitly server approved.
|
||||||
|
ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities());
|
||||||
|
return createOutput(Success, createTransferResponse(targetId, newResource.getTransferData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,28 +14,87 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import google.registry.flows.ResourceTransferCancelFlow;
|
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
|
import google.registry.flows.exceptions.NotPendingTransferException;
|
||||||
|
import google.registry.flows.exceptions.NotTransferInitiatorException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Transfer;
|
import google.registry.model.contact.ContactCommand.Transfer;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.ContactResource.Builder;
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
|
import google.registry.model.poll.PollMessage;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
|
import google.registry.model.transfer.TransferStatus;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that cancels a pending transfer on a {@link ContactResource}.
|
* An EPP flow that cancels a pending transfer on a {@link ContactResource}.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||||
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||||
* @error {@link google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
* @error {@link google.registry.flows.exceptions.NotTransferInitiatorException}
|
||||||
* @error {@link google.registry.flows.ResourceTransferCancelFlow.NotTransferInitiatorException}
|
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
|
||||||
*/
|
*/
|
||||||
public class ContactTransferCancelFlow
|
public class ContactTransferCancelFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
extends ResourceTransferCancelFlow<ContactResource, Builder, Transfer> {
|
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactTransferCancelFlow() {}
|
@Inject ContactTransferCancelFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
return HistoryEntry.Type.CONTACT_TRANSFER_CANCEL;
|
registerExtensions(MetadataExtension.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final EppOutput run() throws EppException {
|
||||||
|
Transfer command = (Transfer) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
// Fail if the object doesn't exist or was deleted.
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
verifyOptionalAuthInfoForResource(authInfo, existingResource);
|
||||||
|
// Fail if object doesn't have a pending transfer, or if authinfo doesn't match. */
|
||||||
|
if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
|
||||||
|
throw new NotPendingTransferException(targetId);
|
||||||
|
}
|
||||||
|
// TODO(b/18997997): Determine if authInfo is necessary to cancel a transfer.
|
||||||
|
if (!clientId.equals(existingResource.getTransferData().getGainingClientId())) {
|
||||||
|
throw new NotTransferInitiatorException();
|
||||||
|
}
|
||||||
|
ContactResource newResource = existingResource.asBuilder()
|
||||||
|
.clearPendingTransfer(TransferStatus.CLIENT_CANCELLED, now)
|
||||||
|
.build();
|
||||||
|
HistoryEntry historyEntry = historyBuilder
|
||||||
|
.setType(HistoryEntry.Type.CONTACT_TRANSFER_CANCEL)
|
||||||
|
.setModificationTime(now)
|
||||||
|
.setParent(Key.create(existingResource))
|
||||||
|
.build();
|
||||||
|
// Create a poll message for the losing client.
|
||||||
|
PollMessage losingPollMessage =
|
||||||
|
createLosingTransferPollMessage(targetId, newResource.getTransferData(), historyEntry);
|
||||||
|
ofy().save().<Object>entities(newResource, historyEntry, losingPollMessage);
|
||||||
|
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||||
|
// been implicitly server approved.
|
||||||
|
ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities());
|
||||||
|
return createOutput(Success, createTransferResponse(targetId, newResource.getTransferData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,62 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import google.registry.flows.ResourceTransferQueryFlow;
|
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||||
|
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Transfer;
|
import google.registry.model.contact.ContactCommand.Transfer;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that queries a pending transfer on a {@link ContactResource}.
|
* An EPP flow that queries a pending transfer on a {@link ContactResource}.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||||
* @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.NoTransferHistoryToQueryException}
|
||||||
* @error {@link google.registry.flows.ResourceTransferQueryFlow.NoTransferHistoryToQueryException}
|
* @error {@link google.registry.flows.exceptions.NotAuthorizedToViewTransferException}
|
||||||
* @error {@link google.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException}
|
* @error {@link google.registry.flows.exceptions.ResourceToQueryDoesNotExistException}
|
||||||
*/
|
*/
|
||||||
public class ContactTransferQueryFlow extends ResourceTransferQueryFlow<ContactResource, Transfer> {
|
public class ContactTransferQueryFlow extends LoggedInFlow {
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
@Inject ContactTransferQueryFlow() {}
|
@Inject ContactTransferQueryFlow() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final EppOutput run() throws EppException {
|
||||||
|
Transfer command = (Transfer) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToQueryDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
verifyOptionalAuthInfoForResource(authInfo, existingResource);
|
||||||
|
// Most of the fields on the transfer response are required, so there's no way to return valid
|
||||||
|
// XML if the object has never been transferred (and hence the fields aren't populated).
|
||||||
|
if (existingResource.getTransferData().getTransferStatus() == null) {
|
||||||
|
throw new NoTransferHistoryToQueryException();
|
||||||
|
}
|
||||||
|
// Note that the authorization info on the command (if present) has already been verified. If
|
||||||
|
// it's present, then the other checks are unnecessary.
|
||||||
|
if (command.getAuthInfo() == null
|
||||||
|
&& !clientId.equals(existingResource.getTransferData().getGainingClientId())
|
||||||
|
&& !clientId.equals(existingResource.getTransferData().getLosingClientId())) {
|
||||||
|
throw new NotAuthorizedToViewTransferException();
|
||||||
|
}
|
||||||
|
return createOutput(
|
||||||
|
Success, createTransferResponse(targetId, existingResource.getTransferData()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,29 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import google.registry.flows.ResourceTransferRejectFlow;
|
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
|
import google.registry.flows.exceptions.NotPendingTransferException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Transfer;
|
import google.registry.model.contact.ContactCommand.Transfer;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.ContactResource.Builder;
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
|
import google.registry.model.poll.PollMessage;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
|
import google.registry.model.transfer.TransferStatus;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,16 +44,50 @@ import javax.inject.Inject;
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||||
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||||
* @error {@link google.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
|
||||||
*/
|
*/
|
||||||
public class ContactTransferRejectFlow
|
public class ContactTransferRejectFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
extends ResourceTransferRejectFlow<ContactResource, Builder, Transfer> {
|
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactTransferRejectFlow() {}
|
@Inject ContactTransferRejectFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
return HistoryEntry.Type.CONTACT_TRANSFER_REJECT;
|
registerExtensions(MetadataExtension.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final EppOutput run() throws EppException {
|
||||||
|
Transfer command = (Transfer) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
if (command.getAuthInfo() != null) {
|
||||||
|
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
|
||||||
|
}
|
||||||
|
if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
|
||||||
|
throw new NotPendingTransferException(targetId);
|
||||||
|
}
|
||||||
|
verifyResourceOwnership(clientId, existingResource);
|
||||||
|
ContactResource newResource = existingResource.asBuilder()
|
||||||
|
.clearPendingTransfer(TransferStatus.CLIENT_REJECTED, now)
|
||||||
|
.build();
|
||||||
|
HistoryEntry historyEntry = historyBuilder
|
||||||
|
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REJECT)
|
||||||
|
.setModificationTime(now)
|
||||||
|
.setParent(Key.create(existingResource))
|
||||||
|
.build();
|
||||||
|
PollMessage gainingPollMessage =
|
||||||
|
createGainingTransferPollMessage(targetId, newResource.getTransferData(), historyEntry);
|
||||||
|
ofy().save().<Object>entities(newResource, historyEntry, gainingPollMessage);
|
||||||
|
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||||
|
// been implicitly server approved.
|
||||||
|
ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities());
|
||||||
|
return createOutput(Success, createTransferResponse(targetId, newResource.getTransferData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,35 +14,136 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import google.registry.config.RegistryEnvironment;
|
import static google.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
|
||||||
import google.registry.flows.ResourceTransferRequestFlow;
|
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createGainingTransferPollMessage;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createLosingTransferPollMessage;
|
||||||
|
import static google.registry.flows.contact.ContactFlowUtils.createTransferResponse;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import google.registry.config.ConfigModule.Config;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
|
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||||
|
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
|
||||||
|
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
import google.registry.model.contact.ContactCommand.Transfer;
|
import google.registry.model.contact.ContactCommand.Transfer;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
|
import google.registry.model.poll.PollMessage;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
|
import google.registry.model.transfer.TransferData;
|
||||||
|
import google.registry.model.transfer.TransferStatus;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that requests a transfer on a {@link ContactResource}.
|
* An EPP flow that requests a transfer on a {@link ContactResource}.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||||
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
|
||||||
* @error {@link google.registry.flows.ResourceTransferRequestFlow.AlreadyPendingTransferException}
|
* @error {@link google.registry.flows.exceptions.AlreadyPendingTransferException}
|
||||||
* @error {@link google.registry.flows.ResourceTransferRequestFlow.MissingTransferRequestAuthInfoException}
|
* @error {@link google.registry.flows.exceptions.MissingTransferRequestAuthInfoException}
|
||||||
* @error {@link google.registry.flows.ResourceTransferRequestFlow.ObjectAlreadySponsoredException}
|
* @error {@link google.registry.flows.exceptions.ObjectAlreadySponsoredException}
|
||||||
*/
|
*/
|
||||||
public class ContactTransferRequestFlow
|
public class ContactTransferRequestFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
extends ResourceTransferRequestFlow<ContactResource, Transfer> {
|
|
||||||
|
|
||||||
|
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||||
|
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||||
|
StatusValue.PENDING_DELETE,
|
||||||
|
StatusValue.SERVER_TRANSFER_PROHIBITED);
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject @ClientId String gainingClientId;
|
||||||
|
@Inject @Config("contactAutomaticTransferLength") Duration automaticTransferLength;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactTransferRequestFlow() {}
|
@Inject ContactTransferRequestFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
return HistoryEntry.Type.CONTACT_TRANSFER_REQUEST;
|
registerExtensions(MetadataExtension.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Duration getAutomaticTransferLength() {
|
protected final EppOutput run() throws EppException {
|
||||||
return RegistryEnvironment.get().config().getContactAutomaticTransferLength();
|
Transfer command = (Transfer) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
if (!authInfo.isPresent()) {
|
||||||
|
throw new MissingTransferRequestAuthInfoException();
|
||||||
|
}
|
||||||
|
verifyAuthInfoForResource(authInfo.get(), existingResource);
|
||||||
|
// Verify that the resource does not already have a pending transfer.
|
||||||
|
if (TransferStatus.PENDING.equals(existingResource.getTransferData().getTransferStatus())) {
|
||||||
|
throw new AlreadyPendingTransferException(targetId);
|
||||||
|
}
|
||||||
|
String losingClientId = existingResource.getCurrentSponsorClientId();
|
||||||
|
// Verify that this client doesn't already sponsor this resource.
|
||||||
|
if (gainingClientId.equals(losingClientId)) {
|
||||||
|
throw new ObjectAlreadySponsoredException();
|
||||||
|
}
|
||||||
|
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
|
||||||
|
HistoryEntry historyEntry = historyBuilder
|
||||||
|
.setType(HistoryEntry.Type.CONTACT_TRANSFER_REQUEST)
|
||||||
|
.setModificationTime(now)
|
||||||
|
.setParent(Key.create(existingResource))
|
||||||
|
.build();
|
||||||
|
DateTime transferExpirationTime = now.plus(automaticTransferLength);
|
||||||
|
TransferData serverApproveTransferData = new TransferData.Builder()
|
||||||
|
.setTransferRequestTime(now)
|
||||||
|
.setTransferRequestTrid(trid)
|
||||||
|
.setGainingClientId(gainingClientId)
|
||||||
|
.setLosingClientId(losingClientId)
|
||||||
|
.setPendingTransferExpirationTime(transferExpirationTime)
|
||||||
|
.setTransferStatus(TransferStatus.SERVER_APPROVED)
|
||||||
|
.build();
|
||||||
|
// If the transfer is server approved, this message will be sent to the losing registrar. */
|
||||||
|
PollMessage serverApproveLosingPollMessage =
|
||||||
|
createLosingTransferPollMessage(targetId, serverApproveTransferData, historyEntry);
|
||||||
|
// If the transfer is server approved, this message will be sent to the gaining registrar. */
|
||||||
|
PollMessage serverApproveGainingPollMessage =
|
||||||
|
createGainingTransferPollMessage(targetId, serverApproveTransferData, historyEntry);
|
||||||
|
TransferData pendingTransferData = serverApproveTransferData.asBuilder()
|
||||||
|
.setTransferStatus(TransferStatus.PENDING)
|
||||||
|
.setServerApproveEntities(
|
||||||
|
ImmutableSet.<Key<? extends TransferData.TransferServerApproveEntity>>of(
|
||||||
|
Key.create(serverApproveGainingPollMessage),
|
||||||
|
Key.create(serverApproveLosingPollMessage)))
|
||||||
|
.build();
|
||||||
|
// When a transfer is requested, a poll message is created to notify the losing registrar.
|
||||||
|
PollMessage requestPollMessage =
|
||||||
|
createLosingTransferPollMessage(targetId, pendingTransferData, historyEntry).asBuilder()
|
||||||
|
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
|
||||||
|
.build();
|
||||||
|
ContactResource newResource = existingResource.asBuilder()
|
||||||
|
.setTransferData(pendingTransferData)
|
||||||
|
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||||
|
.build();
|
||||||
|
ofy().save().<Object>entities(
|
||||||
|
newResource,
|
||||||
|
historyEntry,
|
||||||
|
requestPollMessage,
|
||||||
|
serverApproveGainingPollMessage,
|
||||||
|
serverApproveLosingPollMessage);
|
||||||
|
return createOutput(
|
||||||
|
SuccessWithActionPending, createTransferResponse(targetId, newResource.getTransferData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,36 @@
|
||||||
|
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
|
||||||
|
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||||
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
import static google.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||||
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
import static google.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.flows.ResourceUpdateFlow;
|
import google.registry.flows.FlowModule.ClientId;
|
||||||
|
import google.registry.flows.LoggedInFlow;
|
||||||
|
import google.registry.flows.TransactionalFlow;
|
||||||
|
import google.registry.flows.exceptions.AddRemoveSameValueEppException;
|
||||||
|
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||||
|
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
|
||||||
|
import google.registry.flows.exceptions.StatusNotClientSettableException;
|
||||||
import google.registry.model.contact.ContactCommand.Update;
|
import google.registry.model.contact.ContactCommand.Update;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.contact.ContactResource.Builder;
|
import google.registry.model.contact.ContactResource.Builder;
|
||||||
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand;
|
||||||
|
import google.registry.model.eppinput.ResourceCommand.AddRemoveSameValueException;
|
||||||
|
import google.registry.model.eppoutput.EppOutput;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@ -29,30 +51,81 @@ import javax.inject.Inject;
|
||||||
* An EPP flow that updates a contact resource.
|
* An EPP flow that updates a contact resource.
|
||||||
*
|
*
|
||||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||||
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
* @error {@link google.registry.flows.exceptions.AddRemoveSameValueEppException}
|
||||||
* @error {@link google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException}
|
* @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
|
||||||
* @error {@link google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException}
|
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
|
||||||
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
|
||||||
|
* @error {@link google.registry.flows.exceptions.StatusNotClientSettableException}
|
||||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||||
*/
|
*/
|
||||||
public class ContactUpdateFlow extends ResourceUpdateFlow<ContactResource, Builder, Update> {
|
public class ContactUpdateFlow extends LoggedInFlow implements TransactionalFlow {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
|
||||||
|
* requires special checking, since you must be able to clear the status off the object with an
|
||||||
|
* update.
|
||||||
|
*/
|
||||||
|
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
|
||||||
|
StatusValue.PENDING_DELETE,
|
||||||
|
StatusValue.SERVER_UPDATE_PROHIBITED);
|
||||||
|
|
||||||
|
@Inject ResourceCommand resourceCommand;
|
||||||
|
@Inject Optional<AuthInfo> authInfo;
|
||||||
|
@Inject @ClientId String clientId;
|
||||||
|
@Inject HistoryEntry.Builder historyBuilder;
|
||||||
@Inject ContactUpdateFlow() {}
|
@Inject ContactUpdateFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void verifyNewUpdatedStateIsAllowed() throws EppException {
|
protected final void initLoggedInFlow() throws EppException {
|
||||||
|
registerExtensions(MetadataExtension.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final EppOutput run() throws EppException {
|
||||||
|
Update command = (Update) resourceCommand;
|
||||||
|
String targetId = command.getTargetId();
|
||||||
|
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
|
||||||
|
if (existingResource == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);
|
||||||
|
}
|
||||||
|
verifyOptionalAuthInfoForResource(authInfo, existingResource);
|
||||||
|
if (!isSuperuser) {
|
||||||
|
verifyResourceOwnership(clientId, existingResource);
|
||||||
|
}
|
||||||
|
for (StatusValue statusValue : Sets.union(
|
||||||
|
command.getInnerAdd().getStatusValues(),
|
||||||
|
command.getInnerRemove().getStatusValues())) {
|
||||||
|
if (!isSuperuser && !statusValue.isClientSettable()) { // The superuser can set any status.
|
||||||
|
throw new StatusNotClientSettableException(statusValue.getXmlName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
|
||||||
|
historyBuilder
|
||||||
|
.setType(HistoryEntry.Type.CONTACT_UPDATE)
|
||||||
|
.setModificationTime(now)
|
||||||
|
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||||
|
.setParent(Key.create(existingResource));
|
||||||
|
Builder builder = existingResource.asBuilder();
|
||||||
|
try {
|
||||||
|
command.applyTo(builder);
|
||||||
|
} catch (AddRemoveSameValueException e) {
|
||||||
|
throw new AddRemoveSameValueEppException();
|
||||||
|
}
|
||||||
|
ContactResource newResource = builder
|
||||||
|
.setLastEppUpdateTime(now)
|
||||||
|
.setLastEppUpdateClientId(clientId)
|
||||||
|
.build();
|
||||||
|
// If the resource is marked with clientUpdateProhibited, and this update did not clear that
|
||||||
|
// status, then the update must be disallowed (unless a superuser is requesting the change).
|
||||||
|
if (!isSuperuser
|
||||||
|
&& existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||||
|
&& newResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
||||||
|
throw new ResourceHasClientUpdateProhibitedException();
|
||||||
|
}
|
||||||
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
|
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
|
||||||
validateContactAgainstPolicy(newResource);
|
validateContactAgainstPolicy(newResource);
|
||||||
}
|
ofy().save().<Object>entities(newResource, historyBuilder.build());
|
||||||
|
return createOutput(Success);
|
||||||
@Override
|
|
||||||
protected boolean storeXmlInHistoryEntry() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
|
||||||
return HistoryEntry.Type.CONTACT_UPDATE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,12 +51,14 @@ import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||||
import google.registry.flows.EppException.UnimplementedOptionException;
|
import google.registry.flows.EppException.UnimplementedOptionException;
|
||||||
import google.registry.flows.ResourceCreateFlow;
|
import google.registry.flows.ResourceCreateFlow;
|
||||||
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
import google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException;
|
||||||
|
import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.DomainBase.Builder;
|
import google.registry.model.domain.DomainBase.Builder;
|
||||||
import google.registry.model.domain.DomainCommand.Create;
|
import google.registry.model.domain.DomainCommand.Create;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
import google.registry.model.domain.LrpToken;
|
import google.registry.model.domain.LrpToken;
|
||||||
import google.registry.model.domain.fee.FeeTransformCommandExtension;
|
import google.registry.model.domain.fee.FeeTransformCommandExtension;
|
||||||
|
import google.registry.model.domain.flags.FlagsCreateCommandExtension;
|
||||||
import google.registry.model.domain.launch.LaunchCreateExtension;
|
import google.registry.model.domain.launch.LaunchCreateExtension;
|
||||||
import google.registry.model.domain.launch.LaunchNotice;
|
import google.registry.model.domain.launch.LaunchNotice;
|
||||||
import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException;
|
import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException;
|
||||||
|
@ -67,8 +69,6 @@ import google.registry.model.registry.Registry;
|
||||||
import google.registry.model.registry.Registry.TldState;
|
import google.registry.model.registry.Registry.TldState;
|
||||||
import google.registry.model.smd.SignedMark;
|
import google.registry.model.smd.SignedMark;
|
||||||
import google.registry.model.tmch.ClaimsListShard;
|
import google.registry.model.tmch.ClaimsListShard;
|
||||||
import google.registry.pricing.TldSpecificLogicProxy;
|
|
||||||
import google.registry.pricing.TldSpecificLogicProxy.EppCommandOperations;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -95,16 +95,20 @@ public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Build
|
||||||
protected TldState tldState;
|
protected TldState tldState;
|
||||||
protected Optional<LrpToken> lrpToken;
|
protected Optional<LrpToken> lrpToken;
|
||||||
|
|
||||||
|
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void initResourceCreateOrMutateFlow() throws EppException {
|
public final void initResourceCreateOrMutateFlow() throws EppException {
|
||||||
command = cloneAndLinkReferences(command, now);
|
command = cloneAndLinkReferences(command, now);
|
||||||
registerExtensions(SecDnsCreateExtension.class);
|
registerExtensions(SecDnsCreateExtension.class, FlagsCreateCommandExtension.class);
|
||||||
|
registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
secDnsCreate = eppInput.getSingleExtension(SecDnsCreateExtension.class);
|
secDnsCreate = eppInput.getSingleExtension(SecDnsCreateExtension.class);
|
||||||
launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class);
|
launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class);
|
||||||
feeCreate =
|
feeCreate =
|
||||||
eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
eppInput.getFirstExtensionOfClasses(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty();
|
hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty();
|
||||||
initDomainCreateFlow();
|
initDomainCreateFlow();
|
||||||
|
// We can't initialize extraFlowLogic here, because the TLD has not been checked yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -181,9 +185,19 @@ public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Build
|
||||||
Registry registry = Registry.get(tld);
|
Registry registry = Registry.get(tld);
|
||||||
tldState = registry.getTldState(now);
|
tldState = registry.getTldState(now);
|
||||||
checkRegistryStateForTld(tld);
|
checkRegistryStateForTld(tld);
|
||||||
|
// Now that the TLD has been verified, we can go ahead and initialize extraFlowLogic. The
|
||||||
|
// initialization and matching commit are done at the topmost possible level in the flow
|
||||||
|
// hierarchy, but the actual processing takes place only when needed in the children, e.g.
|
||||||
|
// DomainCreateFlow.
|
||||||
|
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForTld(tld);
|
||||||
domainLabel = domainName.parts().get(0);
|
domainLabel = domainName.parts().get(0);
|
||||||
commandOperations = TldSpecificLogicProxy.getCreatePrice(
|
commandOperations = TldSpecificLogicProxy.getCreatePrice(
|
||||||
registry, domainName.toString(), now, command.getPeriod().getValue());
|
registry,
|
||||||
|
domainName.toString(),
|
||||||
|
getClientId(),
|
||||||
|
now,
|
||||||
|
command.getPeriod().getValue(),
|
||||||
|
eppInput);
|
||||||
// The TLD should always be the parent of the requested domain name.
|
// The TLD should always be the parent of the requested domain name.
|
||||||
isAnchorTenantViaReservation = matchesAnchorTenantReservation(
|
isAnchorTenantViaReservation = matchesAnchorTenantReservation(
|
||||||
domainLabel, tld, command.getAuthInfo().getPw().getValue());
|
domainLabel, tld, command.getAuthInfo().getPw().getValue());
|
||||||
|
@ -252,6 +266,9 @@ public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Build
|
||||||
.setRedemptionHistoryEntry(Key.create(historyEntry))
|
.setRedemptionHistoryEntry(Key.create(historyEntry))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().commitAdditionalLogicChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Validate the secDNS extension, if present. */
|
/** Validate the secDNS extension, if present. */
|
||||||
|
|
|
@ -26,6 +26,7 @@ import static google.registry.flows.domain.DomainFlowUtils.validateNoDuplicateCo
|
||||||
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
|
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
|
||||||
import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent;
|
import static google.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent;
|
||||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
||||||
|
import static google.registry.model.domain.fee.Fee.FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER;
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
@ -37,11 +38,13 @@ import google.registry.flows.ResourceUpdateFlow;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.DomainBase.Builder;
|
import google.registry.model.domain.DomainBase.Builder;
|
||||||
import google.registry.model.domain.DomainCommand.Update;
|
import google.registry.model.domain.DomainCommand.Update;
|
||||||
|
import google.registry.model.domain.fee.FeeTransformCommandExtension;
|
||||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
||||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that updates a domain application or resource.
|
* An EPP flow that updates a domain application or resource.
|
||||||
|
@ -52,18 +55,19 @@ import java.util.Set;
|
||||||
public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Builder<R, B>>
|
public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Builder<R, B>>
|
||||||
extends ResourceUpdateFlow<R, B, Update> {
|
extends ResourceUpdateFlow<R, B, Update> {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected FeeTransformCommandExtension feeUpdate;
|
||||||
|
|
||||||
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void initResourceCreateOrMutateFlow() throws EppException {
|
public final void initResourceCreateOrMutateFlow() throws EppException {
|
||||||
|
registerExtensions(FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
|
feeUpdate =
|
||||||
|
eppInput.getFirstExtensionOfClasses(FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
command = cloneAndLinkReferences(command, now);
|
command = cloneAndLinkReferences(command, now);
|
||||||
initDomainUpdateFlow();
|
initDomainUpdateFlow();
|
||||||
// In certain conditions (for instance, errors), there is no existing resource.
|
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
|
||||||
if (existingResource == null) {
|
|
||||||
extraFlowLogic = Optional.absent();
|
|
||||||
} else {
|
|
||||||
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
@ -143,6 +147,18 @@ public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Build
|
||||||
validateNameserversCountForTld(newResource.getTld(), newResource.getNameservers().size());
|
validateNameserversCountForTld(newResource.getTld(), newResource.getNameservers().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Call the subclass method, then commit any extra flow logic. */
|
||||||
|
@Override
|
||||||
|
protected final void modifyRelatedResources() {
|
||||||
|
modifyUpdateRelatedResources();
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().commitAdditionalLogicChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Modify any other resources that need to be informed of this update. */
|
||||||
|
protected void modifyUpdateRelatedResources() {}
|
||||||
|
|
||||||
/** The secDNS:all element must have value 'true' if present. */
|
/** The secDNS:all element must have value 'true' if present. */
|
||||||
static class SecDnsAllUsageException extends ParameterValuePolicyErrorException {
|
static class SecDnsAllUsageException extends ParameterValuePolicyErrorException {
|
||||||
public SecDnsAllUsageException() {
|
public SecDnsAllUsageException() {
|
||||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.flows.domain;
|
||||||
|
|
||||||
import static google.registry.flows.domain.DomainFlowUtils.DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
import static google.registry.flows.domain.DomainFlowUtils.DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||||
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||||
import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER;
|
|
||||||
import static google.registry.model.eppoutput.Result.Code.Success;
|
import static google.registry.model.eppoutput.Result.Code.Success;
|
||||||
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
|
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
|
||||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||||
|
@ -118,7 +117,6 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainAppl
|
||||||
@Override
|
@Override
|
||||||
protected void initDomainCreateFlow() {
|
protected void initDomainCreateFlow() {
|
||||||
registerExtensions(LaunchCreateExtension.class);
|
registerExtensions(LaunchCreateExtension.class);
|
||||||
registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -215,6 +213,7 @@ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainAppl
|
||||||
responseExtensionsBuilder.add(feeCreate.createResponseBuilder()
|
responseExtensionsBuilder.add(feeCreate.createResponseBuilder()
|
||||||
.setCurrency(commandOperations.getCurrency())
|
.setCurrency(commandOperations.getCurrency())
|
||||||
.setFees(commandOperations.getFees())
|
.setFees(commandOperations.getFees())
|
||||||
|
.setCredits(commandOperations.getCredits())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ import static google.registry.model.index.DomainApplicationIndex.loadActiveAppli
|
||||||
import static google.registry.model.registry.label.ReservationType.UNRESERVED;
|
import static google.registry.model.registry.label.ReservationType.UNRESERVED;
|
||||||
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
|
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
|
||||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
import static google.registry.util.DomainNameUtils.getTldFromDomainName;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
|
@ -49,7 +48,6 @@ import google.registry.model.registry.label.ReservationType;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import org.joda.money.CurrencyUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An EPP flow that checks whether a domain can be provisioned.
|
* An EPP flow that checks whether a domain can be provisioned.
|
||||||
|
@ -160,7 +158,6 @@ public class DomainCheckFlow extends BaseDomainCheckFlow {
|
||||||
if (feeCheck == null) {
|
if (feeCheck == null) {
|
||||||
return null; // No fee checks were requested.
|
return null; // No fee checks were requested.
|
||||||
}
|
}
|
||||||
CurrencyUnit topLevelCurrency = feeCheck.isCurrencySupported() ? feeCheck.getCurrency() : null;
|
|
||||||
ImmutableList.Builder<FeeCheckResponseExtensionItem> feeCheckResponseItemsBuilder =
|
ImmutableList.Builder<FeeCheckResponseExtensionItem> feeCheckResponseItemsBuilder =
|
||||||
new ImmutableList.Builder<>();
|
new ImmutableList.Builder<>();
|
||||||
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
|
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
|
||||||
|
@ -169,10 +166,11 @@ public class DomainCheckFlow extends BaseDomainCheckFlow {
|
||||||
handleFeeRequest(
|
handleFeeRequest(
|
||||||
feeCheckItem,
|
feeCheckItem,
|
||||||
builder,
|
builder,
|
||||||
domainName,
|
domainNames.get(domainName),
|
||||||
getTldFromDomainName(domainName),
|
getClientId(),
|
||||||
topLevelCurrency,
|
feeCheck.getCurrency(),
|
||||||
now);
|
now,
|
||||||
|
eppInput);
|
||||||
feeCheckResponseItemsBuilder
|
feeCheckResponseItemsBuilder
|
||||||
.add(builder.setDomainNameIfSupported(domainName).build());
|
.add(builder.setDomainNameIfSupported(domainName).build());
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,9 @@ package google.registry.flows.domain;
|
||||||
|
|
||||||
import static com.google.common.collect.Sets.union;
|
import static com.google.common.collect.Sets.union;
|
||||||
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||||
import static google.registry.model.domain.fee.Fee.FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER;
|
|
||||||
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
|
import static google.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
|
@ -127,7 +125,6 @@ public class DomainCreateFlow extends DomainCreateOrAllocateFlow {
|
||||||
@Override
|
@Override
|
||||||
protected final void initDomainCreateOrAllocateFlow() {
|
protected final void initDomainCreateOrAllocateFlow() {
|
||||||
registerExtensions(LaunchCreateExtension.class);
|
registerExtensions(LaunchCreateExtension.class);
|
||||||
registerExtensions(FEE_CREATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -190,6 +187,17 @@ public class DomainCreateFlow extends DomainCreateOrAllocateFlow {
|
||||||
.setLaunchNotice(launchCreate.getNotice())
|
.setLaunchNotice(launchCreate.getNotice())
|
||||||
.setSmdId(signedMark == null ? null : signedMark.getId());
|
.setSmdId(signedMark == null ? null : signedMark.getId());
|
||||||
}
|
}
|
||||||
|
// Handle extra flow logic, if any. The initialization and commit are performed higher up in the
|
||||||
|
// flow hierarchy, in BaseDomainCreateFlow.
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().performAdditionalDomainCreateLogic(
|
||||||
|
existingResource,
|
||||||
|
getClientId(),
|
||||||
|
now,
|
||||||
|
command.getPeriod().getValue(),
|
||||||
|
eppInput,
|
||||||
|
historyEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -110,6 +110,7 @@ public abstract class DomainCreateOrAllocateFlow
|
||||||
feeCreate.createResponseBuilder()
|
feeCreate.createResponseBuilder()
|
||||||
.setCurrency(commandOperations.getCurrency())
|
.setCurrency(commandOperations.getCurrency())
|
||||||
.setFees(commandOperations.getFees())
|
.setFees(commandOperations.getFees())
|
||||||
|
.setCredits(commandOperations.getCredits())
|
||||||
.build()));
|
.build()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
||||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
|
@ -36,6 +37,7 @@ import google.registry.model.domain.DomainCommand.Delete;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
import google.registry.model.domain.DomainResource.Builder;
|
import google.registry.model.domain.DomainResource.Builder;
|
||||||
import google.registry.model.domain.GracePeriod;
|
import google.registry.model.domain.GracePeriod;
|
||||||
|
import google.registry.model.domain.fee.BaseFee.FeeType;
|
||||||
import google.registry.model.domain.fee.Credit;
|
import google.registry.model.domain.fee.Credit;
|
||||||
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
||||||
import google.registry.model.domain.fee06.FeeDeleteResponseExtensionV06;
|
import google.registry.model.domain.fee06.FeeDeleteResponseExtensionV06;
|
||||||
|
@ -76,11 +78,14 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
|
||||||
|
|
||||||
ImmutableList<Credit> credits;
|
ImmutableList<Credit> credits;
|
||||||
|
|
||||||
|
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
||||||
|
|
||||||
@Inject DomainDeleteFlow() {}
|
@Inject DomainDeleteFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initResourceCreateOrMutateFlow() throws EppException {
|
protected void initResourceCreateOrMutateFlow() throws EppException {
|
||||||
registerExtensions(SecDnsUpdateExtension.class);
|
registerExtensions(SecDnsUpdateExtension.class);
|
||||||
|
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -93,7 +98,7 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void setDeleteProperties(Builder builder) {
|
protected final void setDeleteProperties(Builder builder) throws EppException {
|
||||||
// Only set to PENDING_DELETE if this domain is not in the Add Grace Period. If domain is in Add
|
// Only set to PENDING_DELETE if this domain is not in the Add Grace Period. If domain is in Add
|
||||||
// Grace Period, we delete it immediately.
|
// Grace Period, we delete it immediately.
|
||||||
// The base class code already handles the immediate delete case, so we only have to handle the
|
// The base class code already handles the immediate delete case, so we only have to handle the
|
||||||
|
@ -122,6 +127,12 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
|
||||||
getClientId())))
|
getClientId())))
|
||||||
.setDeletePollMessage(Key.create(deletePollMessage));
|
.setDeletePollMessage(Key.create(deletePollMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle extra flow logic, if any.
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().performAdditionalDomainDeleteLogic(
|
||||||
|
existingResource, getClientId(), now, eppInput, historyEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -151,8 +162,7 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
|
||||||
ofy().load().key(checkNotNull(gracePeriod.getOneTimeBillingEvent())).now().getCost();
|
ofy().load().key(checkNotNull(gracePeriod.getOneTimeBillingEvent())).now().getCost();
|
||||||
}
|
}
|
||||||
creditsBuilder.add(Credit.create(
|
creditsBuilder.add(Credit.create(
|
||||||
cost.negated().getAmount(),
|
cost.negated().getAmount(), FeeType.CREDIT, gracePeriod.getType().getXmlName()));
|
||||||
String.format("%s credit", gracePeriod.getType().getXmlName())));
|
|
||||||
creditsCurrencyUnit = cost.getCurrencyUnit();
|
creditsCurrencyUnit = cost.getCurrencyUnit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,6 +175,10 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Bui
|
||||||
// Close the autorenew billing event and poll message. This may delete the poll message.
|
// Close the autorenew billing event and poll message. This may delete the poll message.
|
||||||
updateAutorenewRecurrenceEndTime(existingResource, now);
|
updateAutorenewRecurrenceEndTime(existingResource, now);
|
||||||
|
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().commitAdditionalLogicChanges();
|
||||||
|
}
|
||||||
|
|
||||||
// If there's a pending transfer, the gaining client's autorenew billing
|
// If there's a pending transfer, the gaining client's autorenew billing
|
||||||
// event and poll message will already have been deleted in
|
// event and poll message will already have been deleted in
|
||||||
// ResourceDeleteFlow since it's listed in serverApproveEntities.
|
// ResourceDeleteFlow since it's listed in serverApproveEntities.
|
||||||
|
|
|
@ -61,6 +61,7 @@ import google.registry.model.domain.DomainCommand.CreateOrUpdate;
|
||||||
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
|
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
import google.registry.model.domain.Period;
|
import google.registry.model.domain.Period;
|
||||||
|
import google.registry.model.domain.fee.Credit;
|
||||||
import google.registry.model.domain.fee.Fee;
|
import google.registry.model.domain.fee.Fee;
|
||||||
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
|
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
|
||||||
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
|
||||||
|
@ -71,6 +72,7 @@ import google.registry.model.domain.launch.LaunchExtension;
|
||||||
import google.registry.model.domain.launch.LaunchPhase;
|
import google.registry.model.domain.launch.LaunchPhase;
|
||||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
|
import google.registry.model.eppinput.EppInput;
|
||||||
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||||
import google.registry.model.host.HostResource;
|
import google.registry.model.host.HostResource;
|
||||||
import google.registry.model.mark.Mark;
|
import google.registry.model.mark.Mark;
|
||||||
|
@ -86,7 +88,6 @@ import google.registry.model.smd.AbstractSignedMark;
|
||||||
import google.registry.model.smd.EncodedSignedMark;
|
import google.registry.model.smd.EncodedSignedMark;
|
||||||
import google.registry.model.smd.SignedMark;
|
import google.registry.model.smd.SignedMark;
|
||||||
import google.registry.model.smd.SignedMarkRevocationList;
|
import google.registry.model.smd.SignedMarkRevocationList;
|
||||||
import google.registry.pricing.TldSpecificLogicProxy;
|
|
||||||
import google.registry.tmch.TmchXmlSignature;
|
import google.registry.tmch.TmchXmlSignature;
|
||||||
import google.registry.tmch.TmchXmlSignature.CertificateSignatureException;
|
import google.registry.tmch.TmchXmlSignature.CertificateSignatureException;
|
||||||
import google.registry.util.Idn;
|
import google.registry.util.Idn;
|
||||||
|
@ -563,12 +564,13 @@ public class DomainFlowUtils {
|
||||||
static void handleFeeRequest(
|
static void handleFeeRequest(
|
||||||
FeeQueryCommandExtensionItem feeRequest,
|
FeeQueryCommandExtensionItem feeRequest,
|
||||||
FeeQueryResponseExtensionItem.Builder builder,
|
FeeQueryResponseExtensionItem.Builder builder,
|
||||||
String domainName,
|
InternetDomainName domain,
|
||||||
String tld,
|
String clientIdentifier,
|
||||||
@Nullable CurrencyUnit topLevelCurrency,
|
@Nullable CurrencyUnit topLevelCurrency,
|
||||||
DateTime now) throws EppException {
|
DateTime now,
|
||||||
InternetDomainName domain = InternetDomainName.from(domainName);
|
EppInput eppInput) throws EppException {
|
||||||
Registry registry = Registry.get(tld);
|
String domainNameString = domain.toString();
|
||||||
|
Registry registry = Registry.get(domain.parent().toString());
|
||||||
int years = verifyUnitIsYears(feeRequest.getPeriod()).getValue();
|
int years = verifyUnitIsYears(feeRequest.getPeriod()).getValue();
|
||||||
boolean isSunrise = registry.getTldState(now).equals(TldState.SUNRISE);
|
boolean isSunrise = registry.getTldState(now).equals(TldState.SUNRISE);
|
||||||
|
|
||||||
|
@ -577,7 +579,7 @@ public class DomainFlowUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrencyUnit currency =
|
CurrencyUnit currency =
|
||||||
feeRequest.isCurrencySupported() ? feeRequest.getCurrency() : topLevelCurrency;
|
feeRequest.getCurrency() != null ? feeRequest.getCurrency() : topLevelCurrency;
|
||||||
if ((currency != null) && !currency.equals(registry.getCurrency())) {
|
if ((currency != null) && !currency.equals(registry.getCurrency())) {
|
||||||
throw new CurrencyUnitMismatchException();
|
throw new CurrencyUnitMismatchException();
|
||||||
}
|
}
|
||||||
|
@ -586,11 +588,9 @@ public class DomainFlowUtils {
|
||||||
.setCommand(feeRequest.getCommandName(), feeRequest.getPhase(), feeRequest.getSubphase())
|
.setCommand(feeRequest.getCommandName(), feeRequest.getPhase(), feeRequest.getSubphase())
|
||||||
.setCurrencyIfSupported(registry.getCurrency())
|
.setCurrencyIfSupported(registry.getCurrency())
|
||||||
.setPeriod(feeRequest.getPeriod())
|
.setPeriod(feeRequest.getPeriod())
|
||||||
.setClass(TldSpecificLogicProxy.getFeeClass(domainName, now).orNull());
|
.setClass(TldSpecificLogicProxy.getFeeClass(domainNameString, now).orNull());
|
||||||
|
|
||||||
switch (feeRequest.getCommandName()) {
|
switch (feeRequest.getCommandName()) {
|
||||||
case UNKNOWN:
|
|
||||||
throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
|
|
||||||
case CREATE:
|
case CREATE:
|
||||||
if (isReserved(domain, isSunrise)) { // Don't return a create price for reserved names.
|
if (isReserved(domain, isSunrise)) { // Don't return a create price for reserved names.
|
||||||
builder.setClass("reserved"); // Override whatever class we've set above.
|
builder.setClass("reserved"); // Override whatever class we've set above.
|
||||||
|
@ -598,24 +598,35 @@ public class DomainFlowUtils {
|
||||||
builder.setReasonIfSupported("reserved");
|
builder.setReasonIfSupported("reserved");
|
||||||
} else {
|
} else {
|
||||||
builder.setAvailIfSupported(true);
|
builder.setAvailIfSupported(true);
|
||||||
builder.setFees(
|
builder.setFees(TldSpecificLogicProxy.getCreatePrice(
|
||||||
TldSpecificLogicProxy.getCreatePrice(registry, domainName, now, years).getFees());
|
registry, domainNameString, clientIdentifier, now, years, eppInput).getFees());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case RENEW:
|
||||||
|
builder.setAvailIfSupported(true);
|
||||||
|
builder.setFees(TldSpecificLogicProxy.getRenewPrice(
|
||||||
|
registry, domainNameString, clientIdentifier, now, years, eppInput).getFees());
|
||||||
|
break;
|
||||||
case RESTORE:
|
case RESTORE:
|
||||||
if (years != 1) {
|
if (years != 1) {
|
||||||
throw new RestoresAreAlwaysForOneYearException();
|
throw new RestoresAreAlwaysForOneYearException();
|
||||||
}
|
}
|
||||||
builder.setAvailIfSupported(true);
|
builder.setAvailIfSupported(true);
|
||||||
builder.setFees(
|
builder.setFees(TldSpecificLogicProxy.getRestorePrice(
|
||||||
TldSpecificLogicProxy.getRestorePrice(registry, domainName, now, years).getFees());
|
registry, domainNameString, clientIdentifier, now, eppInput).getFees());
|
||||||
break;
|
break;
|
||||||
// TODO(mountford): handle UPDATE
|
case TRANSFER:
|
||||||
default:
|
|
||||||
// Anything else (transfer|renew) will have a "renew" fee.
|
|
||||||
builder.setAvailIfSupported(true);
|
builder.setAvailIfSupported(true);
|
||||||
builder.setFees(
|
builder.setFees(TldSpecificLogicProxy.getTransferPrice(
|
||||||
TldSpecificLogicProxy.getRenewPrice(registry, domainName, now, years).getFees());
|
registry, domainNameString, clientIdentifier, now, years, eppInput).getFees());
|
||||||
|
break;
|
||||||
|
case UPDATE:
|
||||||
|
builder.setAvailIfSupported(true);
|
||||||
|
builder.setFees(TldSpecificLogicProxy.getUpdatePrice(
|
||||||
|
registry, domainNameString, clientIdentifier, now, eppInput).getFees());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnknownFeeCommandException(feeRequest.getUnparsedCommandName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,6 +657,12 @@ public class DomainFlowUtils {
|
||||||
}
|
}
|
||||||
total = total.add(fee.getCost());
|
total = total.add(fee.getCost());
|
||||||
}
|
}
|
||||||
|
for (Credit credit : feeCommand.getCredits()) {
|
||||||
|
if (!credit.hasDefaultAttributes()) {
|
||||||
|
throw new UnsupportedFeeAttributeException();
|
||||||
|
}
|
||||||
|
total = total.add(credit.getCost());
|
||||||
|
}
|
||||||
|
|
||||||
Money feeTotal = null;
|
Money feeTotal = null;
|
||||||
try {
|
try {
|
||||||
|
@ -953,6 +970,13 @@ public class DomainFlowUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fees must be explicitly acknowledged when performing an update which is not free. */
|
||||||
|
static class FeesRequiredForNonFreeUpdateException extends RequiredParameterMissingException {
|
||||||
|
FeesRequiredForNonFreeUpdateException() {
|
||||||
|
super("Fees must be explicitly acknowledged when performing an update which is not free.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The 'grace-period', 'applied' and 'refundable' fields are disallowed by server policy. */
|
/** The 'grace-period', 'applied' and 'refundable' fields are disallowed by server policy. */
|
||||||
static class UnsupportedFeeAttributeException extends UnimplementedOptionException {
|
static class UnsupportedFeeAttributeException extends UnimplementedOptionException {
|
||||||
UnsupportedFeeAttributeException() {
|
UnsupportedFeeAttributeException() {
|
||||||
|
@ -1030,4 +1054,3 @@ public class DomainFlowUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.net.InternetDomainName;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
import google.registry.model.domain.DomainResource.Builder;
|
import google.registry.model.domain.DomainResource.Builder;
|
||||||
|
@ -97,15 +98,16 @@ public class DomainInfoFlow extends BaseDomainInfoFlow<DomainResource, Builder>
|
||||||
handleFeeRequest(
|
handleFeeRequest(
|
||||||
feeInfo,
|
feeInfo,
|
||||||
builder,
|
builder,
|
||||||
getTargetId(),
|
InternetDomainName.from(getTargetId()),
|
||||||
existingResource.getTld(),
|
getClientId(),
|
||||||
null,
|
null,
|
||||||
now);
|
now,
|
||||||
|
eppInput);
|
||||||
extensions.add(builder.build());
|
extensions.add(builder.build());
|
||||||
}
|
}
|
||||||
// If the TLD uses the flags extension, add it to the info response.
|
// If the TLD uses the flags extension, add it to the info response.
|
||||||
Optional<RegistryExtraFlowLogic> extraLogicManager =
|
Optional<RegistryExtraFlowLogic> extraLogicManager =
|
||||||
RegistryExtraFlowLogicProxy.newInstanceForTld(existingResource.getTld());
|
RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
|
||||||
if (extraLogicManager.isPresent()) {
|
if (extraLogicManager.isPresent()) {
|
||||||
List<String> flags = extraLogicManager.get().getExtensionFlags(
|
List<String> flags = extraLogicManager.get().getExtensionFlags(
|
||||||
existingResource, this.getClientId(), now); // As-of date is always now for info commands.
|
existingResource, this.getClientId(), now); // As-of date is always now for info commands.
|
||||||
|
|
|
@ -28,6 +28,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
||||||
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
|
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
|
@ -84,6 +85,8 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
|
||||||
protected FeeTransformCommandExtension feeRenew;
|
protected FeeTransformCommandExtension feeRenew;
|
||||||
protected Money renewCost;
|
protected Money renewCost;
|
||||||
|
|
||||||
|
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
||||||
|
|
||||||
@Inject DomainRenewFlow() {}
|
@Inject DomainRenewFlow() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -96,6 +99,7 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
|
||||||
registerExtensions(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
registerExtensions(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
feeRenew =
|
feeRenew =
|
||||||
eppInput.getFirstExtensionOfClasses(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
eppInput.getFirstExtensionOfClasses(FEE_RENEW_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
|
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,7 +121,7 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DomainResource createOrMutateResource() {
|
protected DomainResource createOrMutateResource() throws EppException {
|
||||||
DateTime newExpirationTime = leapSafeAddYears(
|
DateTime newExpirationTime = leapSafeAddYears(
|
||||||
existingResource.getRegistrationExpirationTime(), command.getPeriod().getValue());
|
existingResource.getRegistrationExpirationTime(), command.getPeriod().getValue());
|
||||||
// Bill for this explicit renew itself.
|
// Bill for this explicit renew itself.
|
||||||
|
@ -143,6 +147,18 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
|
||||||
.setEventTime(newExpirationTime)
|
.setEventTime(newExpirationTime)
|
||||||
.setParent(historyEntry)
|
.setParent(historyEntry)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
// Handle extra flow logic, if any.
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().performAdditionalDomainRenewLogic(
|
||||||
|
existingResource,
|
||||||
|
getClientId(),
|
||||||
|
now,
|
||||||
|
command.getPeriod().getValue(),
|
||||||
|
eppInput,
|
||||||
|
historyEntry);
|
||||||
|
}
|
||||||
|
|
||||||
ofy().save().<Object>entities(explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
|
ofy().save().<Object>entities(explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
|
||||||
return existingResource.asBuilder()
|
return existingResource.asBuilder()
|
||||||
.setRegistrationExpirationTime(newExpirationTime)
|
.setRegistrationExpirationTime(newExpirationTime)
|
||||||
|
@ -160,6 +176,14 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Ren
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Commit any extra flow logic. */
|
||||||
|
@Override
|
||||||
|
protected final void modifyRelatedResources() {
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().commitAdditionalLogicChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||||
return HistoryEntry.Type.DOMAIN_RENEW;
|
return HistoryEntry.Type.DOMAIN_RENEW;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
||||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.net.InternetDomainName;
|
import com.google.common.net.InternetDomainName;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
|
@ -75,6 +76,7 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
|
||||||
protected FeeTransformCommandExtension feeUpdate;
|
protected FeeTransformCommandExtension feeUpdate;
|
||||||
protected Money restoreCost;
|
protected Money restoreCost;
|
||||||
protected Money renewCost;
|
protected Money renewCost;
|
||||||
|
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
||||||
|
|
||||||
@Inject DomainRestoreRequestFlow() {}
|
@Inject DomainRestoreRequestFlow() {}
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
|
||||||
protected final void initResourceCreateOrMutateFlow() throws EppException {
|
protected final void initResourceCreateOrMutateFlow() throws EppException {
|
||||||
registerExtensions(RgpUpdateExtension.class);
|
registerExtensions(RgpUpdateExtension.class);
|
||||||
registerExtensions(FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
registerExtensions(FEE_UPDATE_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
|
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -155,6 +158,13 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
ofy().save().<Object>entities(restoreEvent, autorenewEvent, autorenewPollMessage, renewEvent);
|
ofy().save().<Object>entities(restoreEvent, autorenewEvent, autorenewPollMessage, renewEvent);
|
||||||
|
|
||||||
|
// Handle extra flow logic, if any.
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().performAdditionalDomainRestoreLogic(
|
||||||
|
existingResource, getClientId(), now, eppInput, historyEntry);
|
||||||
|
}
|
||||||
|
|
||||||
return existingResource.asBuilder()
|
return existingResource.asBuilder()
|
||||||
.setRegistrationExpirationTime(newExpirationTime)
|
.setRegistrationExpirationTime(newExpirationTime)
|
||||||
.setDeletionTime(END_OF_TIME)
|
.setDeletionTime(END_OF_TIME)
|
||||||
|
@ -171,6 +181,10 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainReso
|
||||||
// Update the relevant {@link ForeignKey} to cache the new deletion time.
|
// Update the relevant {@link ForeignKey} to cache the new deletion time.
|
||||||
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
|
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
|
||||||
ofy().delete().key(existingResource.getDeletePollMessage());
|
ofy().delete().key(existingResource.getDeletePollMessage());
|
||||||
|
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().commitAdditionalLogicChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,6 +25,7 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
||||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
|
@ -39,6 +40,7 @@ import google.registry.model.domain.Period;
|
||||||
import google.registry.model.domain.fee.BaseFee.FeeType;
|
import google.registry.model.domain.fee.BaseFee.FeeType;
|
||||||
import google.registry.model.domain.fee.Fee;
|
import google.registry.model.domain.fee.Fee;
|
||||||
import google.registry.model.domain.fee.FeeTransformCommandExtension;
|
import google.registry.model.domain.fee.FeeTransformCommandExtension;
|
||||||
|
import google.registry.model.domain.flags.FlagsTransferCommandExtension;
|
||||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||||
import google.registry.model.poll.PollMessage;
|
import google.registry.model.poll.PollMessage;
|
||||||
import google.registry.model.registry.Registry;
|
import google.registry.model.registry.Registry;
|
||||||
|
@ -88,6 +90,9 @@ public class DomainTransferRequestFlow
|
||||||
/** The amount that this transfer will cost due to the implied renew. */
|
/** The amount that this transfer will cost due to the implied renew. */
|
||||||
private Money renewCost;
|
private Money renewCost;
|
||||||
|
|
||||||
|
/** Extra flow logic instance. */
|
||||||
|
protected Optional<RegistryExtraFlowLogic> extraFlowLogic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional extension from the client specifying how much they think the transfer should cost.
|
* An optional extension from the client specifying how much they think the transfer should cost.
|
||||||
*/
|
*/
|
||||||
|
@ -101,7 +106,8 @@ public class DomainTransferRequestFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void initResourceTransferRequestFlow() {
|
protected final void initResourceTransferRequestFlow() throws EppException {
|
||||||
|
registerExtensions(FlagsTransferCommandExtension.class);
|
||||||
registerExtensions(FEE_TRANSFER_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
registerExtensions(FEE_TRANSFER_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
feeTransfer = eppInput.getFirstExtensionOfClasses(
|
feeTransfer = eppInput.getFirstExtensionOfClasses(
|
||||||
FEE_TRANSFER_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
FEE_TRANSFER_COMMAND_EXTENSIONS_IN_PREFERENCE_ORDER);
|
||||||
|
@ -146,6 +152,7 @@ public class DomainTransferRequestFlow
|
||||||
.setMsg("Domain was auto-renewed.")
|
.setMsg("Domain was auto-renewed.")
|
||||||
.setParent(historyEntry)
|
.setParent(historyEntry)
|
||||||
.build();
|
.build();
|
||||||
|
extraFlowLogic = RegistryExtraFlowLogicProxy.newInstanceForDomain(existingResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -174,12 +181,23 @@ public class DomainTransferRequestFlow
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setTransferDataProperties(TransferData.Builder builder) {
|
protected void setTransferDataProperties(TransferData.Builder builder) throws EppException {
|
||||||
builder
|
builder
|
||||||
.setServerApproveBillingEvent(Key.create(transferBillingEvent))
|
.setServerApproveBillingEvent(Key.create(transferBillingEvent))
|
||||||
.setServerApproveAutorenewEvent(Key.create(gainingClientAutorenewEvent))
|
.setServerApproveAutorenewEvent(Key.create(gainingClientAutorenewEvent))
|
||||||
.setServerApproveAutorenewPollMessage(Key.create(gainingClientAutorenewPollMessage))
|
.setServerApproveAutorenewPollMessage(Key.create(gainingClientAutorenewPollMessage))
|
||||||
.setExtendedRegistrationYears(command.getPeriod().getValue());
|
.setExtendedRegistrationYears(command.getPeriod().getValue());
|
||||||
|
|
||||||
|
// Handle extra flow logic, if any.
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().performAdditionalDomainTransferLogic(
|
||||||
|
existingResource,
|
||||||
|
getClientId(),
|
||||||
|
now,
|
||||||
|
command.getPeriod().getValue(),
|
||||||
|
eppInput,
|
||||||
|
historyEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,6 +251,10 @@ public class DomainTransferRequestFlow
|
||||||
// transfer occurs, then the logic in cloneProjectedAtTime() will move the
|
// transfer occurs, then the logic in cloneProjectedAtTime() will move the
|
||||||
// serverApproveAutoRenewEvent into the autoRenewEvent field.
|
// serverApproveAutoRenewEvent into the autoRenewEvent field.
|
||||||
updateAutorenewRecurrenceEndTime(existingResource, automaticTransferTime);
|
updateAutorenewRecurrenceEndTime(existingResource, automaticTransferTime);
|
||||||
|
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
extraFlowLogic.get().commitAdditionalLogicChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package google.registry.flows.domain;
|
package google.registry.flows.domain;
|
||||||
|
|
||||||
import static com.google.common.collect.Sets.symmetricDifference;
|
import static com.google.common.collect.Sets.symmetricDifference;
|
||||||
|
import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
import static google.registry.util.DateTimeUtils.earliestOf;
|
import static google.registry.util.DateTimeUtils.earliestOf;
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import google.registry.dns.DnsQueue;
|
import google.registry.dns.DnsQueue;
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.domain.DomainFlowUtils.FeesRequiredForNonFreeUpdateException;
|
||||||
|
import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations;
|
||||||
import google.registry.model.billing.BillingEvent;
|
import google.registry.model.billing.BillingEvent;
|
||||||
import google.registry.model.billing.BillingEvent.Reason;
|
import google.registry.model.billing.BillingEvent.Reason;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
|
@ -55,6 +58,8 @@ import org.joda.time.DateTime;
|
||||||
* @error {@link BaseDomainUpdateFlow.SecDnsAllUsageException}
|
* @error {@link BaseDomainUpdateFlow.SecDnsAllUsageException}
|
||||||
* @error {@link BaseDomainUpdateFlow.UrgentAttributeNotSupportedException}
|
* @error {@link BaseDomainUpdateFlow.UrgentAttributeNotSupportedException}
|
||||||
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
|
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
|
||||||
|
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||||
|
* @error {@link DomainFlowUtils.FeesRequiredForNonFreeUpdateException}
|
||||||
* @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException}
|
* @error {@link DomainFlowUtils.LinkedResourcesDoNotExistException}
|
||||||
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
||||||
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
||||||
|
@ -132,13 +137,29 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow<DomainResource, Build
|
||||||
// Handle extra flow logic, if any.
|
// Handle extra flow logic, if any.
|
||||||
if (extraFlowLogic.isPresent()) {
|
if (extraFlowLogic.isPresent()) {
|
||||||
extraFlowLogic.get().performAdditionalDomainUpdateLogic(
|
extraFlowLogic.get().performAdditionalDomainUpdateLogic(
|
||||||
existingResource, getClientId(), now, eppInput);
|
existingResource, getClientId(), now, eppInput, historyEntry);
|
||||||
}
|
}
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void modifyRelatedResources() {
|
protected final void verifyDomainUpdateIsAllowed() throws EppException {
|
||||||
|
EppCommandOperations commandOperations = TldSpecificLogicProxy.getUpdatePrice(
|
||||||
|
Registry.get(existingResource.getTld()),
|
||||||
|
existingResource.getFullyQualifiedDomainName(),
|
||||||
|
getClientId(),
|
||||||
|
now,
|
||||||
|
eppInput);
|
||||||
|
// The fee extension must be present if the update is not free.
|
||||||
|
if ((feeUpdate == null) && !commandOperations.getTotalCost().isZero()) {
|
||||||
|
throw new FeesRequiredForNonFreeUpdateException();
|
||||||
|
}
|
||||||
|
validateFeeChallenge(
|
||||||
|
targetId, existingResource.getTld(), now, feeUpdate, commandOperations.getTotalCost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected final void modifyUpdateRelatedResources() {
|
||||||
// Determine the status changes, and filter to server statuses.
|
// Determine the status changes, and filter to server statuses.
|
||||||
// If any of these statuses have been added or removed, bill once.
|
// If any of these statuses have been added or removed, bill once.
|
||||||
if (metadataExtension != null && metadataExtension.getRequestedByRegistrar()) {
|
if (metadataExtension != null && metadataExtension.getRequestedByRegistrar()) {
|
||||||
|
@ -161,10 +182,6 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow<DomainResource, Build
|
||||||
ofy().save().entity(billingEvent);
|
ofy().save().entity(billingEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extraFlowLogic.isPresent()) {
|
|
||||||
extraFlowLogic.get().commitAdditionalDomainUpdates();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,7 +16,9 @@ package google.registry.flows.domain;
|
||||||
|
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.model.domain.DomainResource;
|
import google.registry.model.domain.DomainResource;
|
||||||
|
import google.registry.model.domain.fee.BaseFee;
|
||||||
import google.registry.model.eppinput.EppInput;
|
import google.registry.model.eppinput.EppInput;
|
||||||
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
@ -26,20 +28,102 @@ import org.joda.time.DateTime;
|
||||||
*/
|
*/
|
||||||
public interface RegistryExtraFlowLogic {
|
public interface RegistryExtraFlowLogic {
|
||||||
|
|
||||||
/** Get the flags to be used in the EPP flags extension. This is used for EPP info commands. */
|
/** Gets the flags to be used in the EPP flags extension. This is used for EPP info commands. */
|
||||||
public List<String> getExtensionFlags(
|
public List<String> getExtensionFlags(
|
||||||
DomainResource domainResource, String clientIdentifier, DateTime asOfDate);
|
DomainResource domainResource, String clientIdentifier, DateTime asOfDate);
|
||||||
|
|
||||||
|
/** Computes the expected creation fee, for use in fee challenges and the like. */
|
||||||
|
public BaseFee getCreateFeeOrCredit(
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput) throws EppException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add and remove flags passed via the EPP flags extension. Any changes should not be persisted to
|
* Performs additional tasks required for a create command. Any changes should not be persisted to
|
||||||
* Datastore until commitAdditionalDomainUpdates is called. Name suggested by Benjamin McIlwain.
|
* Datastore until commitAdditionalLogicChanges is called.
|
||||||
*/
|
*/
|
||||||
public void performAdditionalDomainUpdateLogic(
|
public void performAdditionalDomainCreateLogic(
|
||||||
DomainResource domainResource,
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput,
|
||||||
|
HistoryEntry historyEntry) throws EppException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs additional tasks required for a delete command. Any changes should not be persisted to
|
||||||
|
* Datastore until commitAdditionalLogicChanges is called.
|
||||||
|
*/
|
||||||
|
public void performAdditionalDomainDeleteLogic(
|
||||||
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
EppInput eppInput,
|
||||||
|
HistoryEntry historyEntry) throws EppException;
|
||||||
|
|
||||||
|
/** Computes the expected renewal fee, for use in fee challenges and the like. */
|
||||||
|
public BaseFee getRenewFeeOrCredit(
|
||||||
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput) throws EppException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs additional tasks required for a renew command. Any changes should not be persisted
|
||||||
|
* to Datastore until commitAdditionalLogicChanges is called.
|
||||||
|
*/
|
||||||
|
public void performAdditionalDomainRenewLogic(
|
||||||
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput,
|
||||||
|
HistoryEntry historyEntry) throws EppException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs additional tasks required for a restore command. Any changes should not be persisted
|
||||||
|
* to Datastore until commitAdditionalLogicChanges is called.
|
||||||
|
*/
|
||||||
|
public void performAdditionalDomainRestoreLogic(
|
||||||
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
EppInput eppInput,
|
||||||
|
HistoryEntry historyEntry) throws EppException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs additional tasks required for a transfer command. Any changes should not be persisted
|
||||||
|
* to Datastore until commitAdditionalLogicChanges is called.
|
||||||
|
*/
|
||||||
|
public void performAdditionalDomainTransferLogic(
|
||||||
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput,
|
||||||
|
HistoryEntry historyEntry) throws EppException;
|
||||||
|
|
||||||
|
/** Computes the expected update fee, for use in fee challenges and the like. */
|
||||||
|
public BaseFee getUpdateFeeOrCredit(
|
||||||
|
DomainResource domain,
|
||||||
String clientIdentifier,
|
String clientIdentifier,
|
||||||
DateTime asOfDate,
|
DateTime asOfDate,
|
||||||
EppInput eppInput) throws EppException;
|
EppInput eppInput) throws EppException;
|
||||||
|
|
||||||
/** Commit any changes made as a result of a call to performAdditionalDomainUpdateLogic(). */
|
/**
|
||||||
public void commitAdditionalDomainUpdates();
|
* Performs additional tasks required for an update command. Any changes should not be persisted
|
||||||
|
* to Datastore until commitAdditionalLogicChanges is called.
|
||||||
|
*/
|
||||||
|
public void performAdditionalDomainUpdateLogic(
|
||||||
|
DomainResource domain,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime asOfDate,
|
||||||
|
EppInput eppInput,
|
||||||
|
HistoryEntry historyEntry) throws EppException;
|
||||||
|
|
||||||
|
/** Commits any changes made as a result of a call to one of the performXXX methods. */
|
||||||
|
public void commitAdditionalLogicChanges();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,12 @@
|
||||||
package google.registry.flows.domain;
|
package google.registry.flows.domain;
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.EppException.CommandFailedException;
|
||||||
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.registry.Registry;
|
import google.registry.model.registry.Registry;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static class to return the correct {@link RegistryExtraFlowLogic} for a particular TLD.
|
* Static class to return the correct {@link RegistryExtraFlowLogic} for a particular TLD.
|
||||||
|
@ -36,12 +40,23 @@ public class RegistryExtraFlowLogicProxy {
|
||||||
extraLogicOverrideMap.put(tld, extraLogicClass);
|
extraLogicOverrideMap.put(tld, extraLogicClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<RegistryExtraFlowLogic> newInstanceForTld(String tld) {
|
public static <D extends DomainBase> Optional<RegistryExtraFlowLogic>
|
||||||
|
newInstanceForDomain(@Nullable D domain) throws EppException {
|
||||||
|
if (domain == null) {
|
||||||
|
return Optional.absent();
|
||||||
|
} else {
|
||||||
|
return newInstanceForTld(domain.getTld());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<RegistryExtraFlowLogic>
|
||||||
|
newInstanceForTld(String tld) throws EppException {
|
||||||
if (extraLogicOverrideMap.containsKey(tld)) {
|
if (extraLogicOverrideMap.containsKey(tld)) {
|
||||||
try {
|
try {
|
||||||
return Optional.<RegistryExtraFlowLogic>of(extraLogicOverrideMap.get(tld).newInstance());
|
return Optional.<RegistryExtraFlowLogic>of(
|
||||||
} catch (InstantiationException | IllegalAccessException e) {
|
extraLogicOverrideMap.get(tld).getConstructor().newInstance());
|
||||||
return Optional.absent();
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
throw new CommandFailedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
|
|
297
java/google/registry/flows/domain/TldSpecificLogicProxy.java
Normal file
297
java/google/registry/flows/domain/TldSpecificLogicProxy.java
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
// 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.domain;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
import static google.registry.model.EppResourceUtils.loadByUniqueId;
|
||||||
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
|
||||||
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.googlecode.objectify.Key;
|
||||||
|
import google.registry.flows.EppException;
|
||||||
|
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException;
|
||||||
|
import google.registry.model.ImmutableObject;
|
||||||
|
import google.registry.model.domain.DomainCommand.Create;
|
||||||
|
import google.registry.model.domain.DomainResource;
|
||||||
|
import google.registry.model.domain.LrpToken;
|
||||||
|
import google.registry.model.domain.fee.BaseFee;
|
||||||
|
import google.registry.model.domain.fee.BaseFee.FeeType;
|
||||||
|
import google.registry.model.domain.fee.Credit;
|
||||||
|
import google.registry.model.domain.fee.EapFee;
|
||||||
|
import google.registry.model.domain.fee.Fee;
|
||||||
|
import google.registry.model.eppinput.EppInput;
|
||||||
|
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
|
||||||
|
import google.registry.model.registry.Registry;
|
||||||
|
import java.util.List;
|
||||||
|
import org.joda.money.CurrencyUnit;
|
||||||
|
import org.joda.money.Money;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides pricing, billing, and update logic, with call-outs that can be customized by providing
|
||||||
|
* implementations on a per-TLD basis.
|
||||||
|
*/
|
||||||
|
public final class TldSpecificLogicProxy {
|
||||||
|
/** A collection of fees and credits for a specific EPP transform. */
|
||||||
|
public static final class EppCommandOperations extends ImmutableObject {
|
||||||
|
private final CurrencyUnit currency;
|
||||||
|
private final ImmutableList<Fee> fees;
|
||||||
|
private final ImmutableList<Credit> credits;
|
||||||
|
|
||||||
|
/** Constructs an EppCommandOperations object using separate lists of fees and credits. */
|
||||||
|
EppCommandOperations(
|
||||||
|
CurrencyUnit currency, ImmutableList<Fee> fees, ImmutableList<Credit> credits) {
|
||||||
|
this.currency = checkArgumentNotNull(
|
||||||
|
currency, "Currency may not be null in EppCommandOperations.");
|
||||||
|
checkArgument(!fees.isEmpty(), "You must specify one or more fees.");
|
||||||
|
this.fees = checkArgumentNotNull(fees, "Fees may not be null in EppCommandOperations.");
|
||||||
|
this.credits =
|
||||||
|
checkArgumentNotNull(credits, "Credits may not be null in EppCommandOperations.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an EppCommandOperations object. The arguments are sorted into fees and credits.
|
||||||
|
*/
|
||||||
|
EppCommandOperations(CurrencyUnit currency, BaseFee... feesAndCredits) {
|
||||||
|
this.currency = checkArgumentNotNull(
|
||||||
|
currency, "Currency may not be null in EppCommandOperations.");
|
||||||
|
ImmutableList.Builder<Fee> feeBuilder = new ImmutableList.Builder<>();
|
||||||
|
ImmutableList.Builder<Credit> creditBuilder = new ImmutableList.Builder<>();
|
||||||
|
for (BaseFee feeOrCredit : feesAndCredits) {
|
||||||
|
if (feeOrCredit instanceof Credit) {
|
||||||
|
creditBuilder.add((Credit) feeOrCredit);
|
||||||
|
} else {
|
||||||
|
feeBuilder.add((Fee) feeOrCredit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.fees = feeBuilder.build();
|
||||||
|
this.credits = creditBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Money getTotalCostForType(FeeType type) {
|
||||||
|
Money result = Money.zero(currency);
|
||||||
|
checkArgumentNotNull(type);
|
||||||
|
for (Fee fee : fees) {
|
||||||
|
if (fee.getType() == type) {
|
||||||
|
result = result.plus(fee.getCost());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the total cost of all fees and credits for the event. */
|
||||||
|
public Money getTotalCost() {
|
||||||
|
Money result = Money.zero(currency);
|
||||||
|
for (Fee fee : fees) {
|
||||||
|
result = result.plus(fee.getCost());
|
||||||
|
}
|
||||||
|
for (Credit credit : credits) {
|
||||||
|
result = result.plus(credit.getCost());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the create cost for the event. */
|
||||||
|
public Money getCreateCost() {
|
||||||
|
return getTotalCostForType(FeeType.CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the EAP cost for the event. */
|
||||||
|
public Money getEapCost() {
|
||||||
|
return getTotalCostForType(FeeType.EAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the list of fees for the event. */
|
||||||
|
public ImmutableList<Fee> getFees() {
|
||||||
|
return fees;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the list of credits for the event. */
|
||||||
|
public List<Credit> getCredits() {
|
||||||
|
return nullToEmpty(credits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the currency for all fees in the event. */
|
||||||
|
public final CurrencyUnit getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TldSpecificLogicProxy() {}
|
||||||
|
|
||||||
|
/** Returns a new create price for the Pricer. */
|
||||||
|
public static EppCommandOperations getCreatePrice(
|
||||||
|
Registry registry,
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime date,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput) throws EppException {
|
||||||
|
CurrencyUnit currency = registry.getCurrency();
|
||||||
|
|
||||||
|
// Get the create cost, either from the extra flow logic or straight from PricingEngineProxy.
|
||||||
|
BaseFee createFeeOrCredit;
|
||||||
|
Optional<RegistryExtraFlowLogic> extraFlowLogic =
|
||||||
|
RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr());
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
createFeeOrCredit = extraFlowLogic.get()
|
||||||
|
.getCreateFeeOrCredit(domainName, clientIdentifier, date, years, eppInput);
|
||||||
|
} else {
|
||||||
|
DomainPrices prices = getPricesForDomainName(domainName, date);
|
||||||
|
createFeeOrCredit =
|
||||||
|
Fee.create(prices.getCreateCost().multipliedBy(years).getAmount(), FeeType.CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fees for the cost and the EAP fee, if any.
|
||||||
|
EapFee eapFee = registry.getEapFeeFor(date);
|
||||||
|
Money eapFeeCost = eapFee.getCost();
|
||||||
|
checkState(eapFeeCost.getCurrencyUnit().equals(currency));
|
||||||
|
if (!eapFeeCost.getAmount().equals(Money.zero(currency).getAmount())) {
|
||||||
|
return new EppCommandOperations(
|
||||||
|
currency,
|
||||||
|
createFeeOrCredit,
|
||||||
|
Fee.create(eapFeeCost.getAmount(), FeeType.EAP, eapFee.getPeriod().upperEndpoint()));
|
||||||
|
} else {
|
||||||
|
return new EppCommandOperations(currency, createFeeOrCredit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the renew fee or credit. This is called by other methods which use the renew fee
|
||||||
|
* (renew, restore, etc).
|
||||||
|
*/
|
||||||
|
static BaseFee getRenewFeeOrCredit(
|
||||||
|
Registry registry,
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime date,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput) throws EppException {
|
||||||
|
Optional<RegistryExtraFlowLogic> extraFlowLogic =
|
||||||
|
RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr());
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
// TODO: Consider changing the method definition to have the domain passed in to begin with.
|
||||||
|
DomainResource domain = loadByUniqueId(DomainResource.class, domainName, date);
|
||||||
|
if (domain == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(DomainResource.class, domainName);
|
||||||
|
}
|
||||||
|
return
|
||||||
|
extraFlowLogic.get().getRenewFeeOrCredit(domain, clientIdentifier, date, years, eppInput);
|
||||||
|
} else {
|
||||||
|
DomainPrices prices = getPricesForDomainName(domainName, date);
|
||||||
|
return Fee.create(prices.getRenewCost().multipliedBy(years).getAmount(), FeeType.RENEW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new renew price for the pricer. */
|
||||||
|
public static EppCommandOperations getRenewPrice(
|
||||||
|
Registry registry,
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime date,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput) throws EppException {
|
||||||
|
return new EppCommandOperations(
|
||||||
|
registry.getCurrency(),
|
||||||
|
getRenewFeeOrCredit(registry, domainName, clientIdentifier, date, years, eppInput));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new restore price for the pricer. */
|
||||||
|
public static EppCommandOperations getRestorePrice(
|
||||||
|
Registry registry,
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime date,
|
||||||
|
EppInput eppInput) throws EppException {
|
||||||
|
return new EppCommandOperations(
|
||||||
|
registry.getCurrency(),
|
||||||
|
getRenewFeeOrCredit(registry, domainName, clientIdentifier, date, 1, eppInput),
|
||||||
|
Fee.create(registry.getStandardRestoreCost().getAmount(), FeeType.RESTORE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new transfer price for the pricer. */
|
||||||
|
public static EppCommandOperations getTransferPrice(
|
||||||
|
Registry registry,
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime transferDate,
|
||||||
|
int years,
|
||||||
|
EppInput eppInput) throws EppException {
|
||||||
|
// Currently, all transfer prices = renew prices, so just pass through.
|
||||||
|
return getRenewPrice(
|
||||||
|
registry, domainName, clientIdentifier, transferDate, years, eppInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new update price for the pricer. */
|
||||||
|
public static EppCommandOperations getUpdatePrice(
|
||||||
|
Registry registry,
|
||||||
|
String domainName,
|
||||||
|
String clientIdentifier,
|
||||||
|
DateTime date,
|
||||||
|
EppInput eppInput) throws EppException {
|
||||||
|
CurrencyUnit currency = registry.getCurrency();
|
||||||
|
|
||||||
|
// If there is extra flow logic, it may specify an update price. Otherwise, there is none.
|
||||||
|
BaseFee feeOrCredit;
|
||||||
|
Optional<RegistryExtraFlowLogic> extraFlowLogic =
|
||||||
|
RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr());
|
||||||
|
if (extraFlowLogic.isPresent()) {
|
||||||
|
// TODO: Consider changing the method definition to have the domain passed in to begin with.
|
||||||
|
DomainResource domain = loadByUniqueId(DomainResource.class, domainName, date);
|
||||||
|
if (domain == null) {
|
||||||
|
throw new ResourceToMutateDoesNotExistException(DomainResource.class, domainName);
|
||||||
|
}
|
||||||
|
feeOrCredit =
|
||||||
|
extraFlowLogic.get().getUpdateFeeOrCredit(domain, clientIdentifier, date, eppInput);
|
||||||
|
} else {
|
||||||
|
feeOrCredit = Fee.create(Money.zero(registry.getCurrency()).getAmount(), FeeType.UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EppCommandOperations(currency, feeOrCredit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the fee class for a given domain and date. */
|
||||||
|
public static Optional<String> getFeeClass(String domainName, DateTime date) {
|
||||||
|
return getPricesForDomainName(domainName, date).getFeeClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a {@link Create} command has a valid {@link LrpToken} for a particular TLD, and
|
||||||
|
* return that token (wrapped in an {@link Optional}) if one exists.
|
||||||
|
*
|
||||||
|
* <p>This method has no knowledge of whether or not an auth code (interpreted here as an LRP
|
||||||
|
* token) has already been checked against the reserved list for QLP (anchor tenant), as auth
|
||||||
|
* codes are used for both types of registrations.
|
||||||
|
*/
|
||||||
|
public static Optional<LrpToken> getMatchingLrpToken(Create createCommand, String tld) {
|
||||||
|
// Note that until the actual per-TLD logic is built out, what's being done here is a basic
|
||||||
|
// domain-name-to-assignee match.
|
||||||
|
String lrpToken = createCommand.getAuthInfo().getPw().getValue();
|
||||||
|
LrpToken token = ofy().load().key(Key.create(LrpToken.class, lrpToken)).now();
|
||||||
|
if (token != null) {
|
||||||
|
if (token.getAssignee().equalsIgnoreCase(createCommand.getFullyQualifiedDomainName())
|
||||||
|
&& token.getRedemptionHistoryEntry() == null
|
||||||
|
&& token.getValidTlds().contains(tld)) {
|
||||||
|
return Optional.of(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.<LrpToken>absent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||||
|
|
||||||
|
/** Extension flag is not currently valid for this domain. */
|
||||||
|
public class ExtensionFlagDomainPolicyErrorException extends StatusProhibitsOperationException {
|
||||||
|
public ExtensionFlagDomainPolicyErrorException(String flag) {
|
||||||
|
super(String.format("Extension flag %s is not valid for this domain", flag));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.RequiredParameterMissingException;
|
||||||
|
|
||||||
|
/** Required extension flag missing. */
|
||||||
|
public class ExtensionFlagMissingException extends RequiredParameterMissingException {
|
||||||
|
public ExtensionFlagMissingException(String flag) {
|
||||||
|
super(String.format("Flag %s must be specified", flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExtensionFlagMissingException(String flag1, String flag2) {
|
||||||
|
super(String.format("Either %s or %s must be specified", flag1, flag2));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** Extension flag is not currently valid for this registrar. */
|
||||||
|
public class ExtensionFlagRegistrarPolicyErrorException extends ParameterValuePolicyErrorException {
|
||||||
|
public ExtensionFlagRegistrarPolicyErrorException(String flag) {
|
||||||
|
super(String.format("Extension flag %s is not valid for this registrar", flag));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||||
|
|
||||||
|
/** Extension flag cannot currently be set for this domain. */
|
||||||
|
public class ExtensionFlagSetDomainPolicyErrorException extends StatusProhibitsOperationException {
|
||||||
|
public ExtensionFlagSetDomainPolicyErrorException(String flag) {
|
||||||
|
super(String.format("Extension flag %s cannot be set for this domain", flag));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||||
|
|
||||||
|
/** Extension flag is not valid. */
|
||||||
|
public class InvalidExtensionFlagException extends ParameterValueRangeErrorException {
|
||||||
|
public InvalidExtensionFlagException(String flag) {
|
||||||
|
super(String.format("Extension flag %s is not defined", flag));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** Specified extension flags are mutually exclusive. */
|
||||||
|
public class MutuallyExclusiveExtensionFlagsException extends ParameterValuePolicyErrorException {
|
||||||
|
public MutuallyExclusiveExtensionFlagsException(String flag1, String flag2) {
|
||||||
|
super(String.format("Extension flags %s and %s are mutually exclusive", flag1, flag2));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** Only client flags can be updated. */
|
||||||
|
public class NonClientFlagException extends ParameterValuePolicyErrorException {
|
||||||
|
public NonClientFlagException() {
|
||||||
|
super("Non-client flags cannot be added or removed");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.domain.flags;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** The same flag was specified in both add and remove lists. */
|
||||||
|
public class SameFlagAddedAndRemovedException extends ParameterValuePolicyErrorException {
|
||||||
|
public SameFlagAddedAndRemovedException() {
|
||||||
|
super("An extension flag cannot be both added and removed in the same command");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** Cannot add and remove the same value. */
|
||||||
|
public class AddRemoveSameValueEppException extends ParameterValuePolicyErrorException {
|
||||||
|
public AddRemoveSameValueEppException() {
|
||||||
|
super("Cannot add and remove the same value");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ObjectPendingTransferException;
|
||||||
|
|
||||||
|
/** The resource is already pending transfer. */
|
||||||
|
public class AlreadyPendingTransferException extends ObjectPendingTransferException {
|
||||||
|
public AlreadyPendingTransferException(String targetId) {
|
||||||
|
super(targetId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.CommandUseErrorException;
|
||||||
|
|
||||||
|
/** Command is not allowed in the current registry phase. */
|
||||||
|
public class BadCommandForRegistryPhaseException extends CommandUseErrorException {
|
||||||
|
public BadCommandForRegistryPhaseException() {
|
||||||
|
super("Command is not allowed in the current registry phase");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||||
|
|
||||||
|
/** Authorization info is required to request a transfer. */
|
||||||
|
public class MissingTransferRequestAuthInfoException extends AuthorizationErrorException {
|
||||||
|
public MissingTransferRequestAuthInfoException() {
|
||||||
|
super("Authorization info is required to request a transfer");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.CommandUseErrorException;
|
||||||
|
|
||||||
|
/** Object has no transfer history. */
|
||||||
|
public class NoTransferHistoryToQueryException extends CommandUseErrorException {
|
||||||
|
public NoTransferHistoryToQueryException() {
|
||||||
|
super("Object has no transfer history");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||||
|
|
||||||
|
/** Registrar is not authorized to view transfer status. */
|
||||||
|
public class NotAuthorizedToViewTransferException extends AuthorizationErrorException {
|
||||||
|
public NotAuthorizedToViewTransferException() {
|
||||||
|
super("Registrar is not authorized to view transfer status");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ObjectNotPendingTransferException;
|
||||||
|
|
||||||
|
/** The resource does not have a pending transfer. */
|
||||||
|
public class NotPendingTransferException extends ObjectNotPendingTransferException {
|
||||||
|
public NotPendingTransferException(String objectId) {
|
||||||
|
super(objectId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||||
|
|
||||||
|
/** Registrar is not the initiator of this transfer. */
|
||||||
|
public class NotTransferInitiatorException extends AuthorizationErrorException {
|
||||||
|
public NotTransferInitiatorException() {
|
||||||
|
super("Registrar is not the initiator of this transfer");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.CommandUseErrorException;
|
||||||
|
|
||||||
|
/** Registrar already sponsors the object of this transfer request. */
|
||||||
|
public class ObjectAlreadySponsoredException extends CommandUseErrorException {
|
||||||
|
public ObjectAlreadySponsoredException() {
|
||||||
|
super("Registrar already sponsors the object of this transfer request");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||||
|
|
||||||
|
/** Only a tool can pass a metadata extension. */
|
||||||
|
public class OnlyToolCanPassMetadataException extends AuthorizationErrorException {
|
||||||
|
public OnlyToolCanPassMetadataException() {
|
||||||
|
super("Metadata extensions can only be passed by tools.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import google.registry.flows.EppException.ObjectAlreadyExistsException;
|
||||||
|
|
||||||
|
/** Resource with this id already exists. */
|
||||||
|
public class ResourceAlreadyExistsException extends ObjectAlreadyExistsException {
|
||||||
|
|
||||||
|
/** Whether this was thrown from a "failfast" context. Useful for testing. */
|
||||||
|
final boolean failfast;
|
||||||
|
|
||||||
|
public ResourceAlreadyExistsException(String resourceId, boolean failfast) {
|
||||||
|
super(String.format("Object with given ID (%s) already exists", resourceId));
|
||||||
|
this.failfast = failfast;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceAlreadyExistsException(String resourceId) {
|
||||||
|
this(resourceId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public boolean isFailfast() {
|
||||||
|
return failfast;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||||
|
|
||||||
|
/** This resource has clientUpdateProhibited on it, and the update does not clear that status. */
|
||||||
|
public class ResourceHasClientUpdateProhibitedException extends StatusProhibitsOperationException {
|
||||||
|
public ResourceHasClientUpdateProhibitedException() {
|
||||||
|
super("Operation disallowed by status: clientUpdateProhibited");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||||
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/** Resource status prohibits this operation. */
|
||||||
|
public class ResourceStatusProhibitsOperationException
|
||||||
|
extends StatusProhibitsOperationException {
|
||||||
|
public ResourceStatusProhibitsOperationException(Set<StatusValue> status) {
|
||||||
|
super("Operation disallowed by status: " + Joiner.on(", ").join(status));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||||
|
|
||||||
|
/** Resource to be deleted has active incoming references. */
|
||||||
|
public class ResourceToDeleteIsReferencedException extends AssociationProhibitsOperationException {
|
||||||
|
public ResourceToDeleteIsReferencedException() {
|
||||||
|
super("Resource to be deleted has active incoming references");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ObjectDoesNotExistException;
|
||||||
|
|
||||||
|
/** Resource with this id does not exist. */
|
||||||
|
public class ResourceToMutateDoesNotExistException extends ObjectDoesNotExistException {
|
||||||
|
public ResourceToMutateDoesNotExistException(Class<?> type, String targetId) {
|
||||||
|
super(type, targetId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ObjectDoesNotExistException;
|
||||||
|
|
||||||
|
/** Resource with this id does not exist. */
|
||||||
|
public class ResourceToQueryDoesNotExistException extends ObjectDoesNotExistException {
|
||||||
|
public ResourceToQueryDoesNotExistException(Class<?> type, String targetId) {
|
||||||
|
super(type, targetId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||||
|
|
||||||
|
/** The specified status value cannot be set by clients. */
|
||||||
|
public class StatusNotClientSettableException extends ParameterValueRangeErrorException {
|
||||||
|
public StatusNotClientSettableException(String statusValue) {
|
||||||
|
super(String.format("Status value %s cannot be set by clients", statusValue));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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.exceptions;
|
||||||
|
|
||||||
|
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||||
|
|
||||||
|
/** Too many resource checks requested in one check command. */
|
||||||
|
public class TooManyResourceChecksException extends ParameterValuePolicyErrorException {
|
||||||
|
public TooManyResourceChecksException(int maxChecks) {
|
||||||
|
super(String.format("No more than %s resources may be checked at a time", maxChecks));
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.eppoutput.EppResponse.ResponseData;
|
import google.registry.model.eppoutput.EppResponse.ResponseData;
|
||||||
import google.registry.model.ofy.CommitLogManifest;
|
import google.registry.model.ofy.CommitLogManifest;
|
||||||
import google.registry.model.transfer.TransferData;
|
import google.registry.model.transfer.TransferData;
|
||||||
|
import google.registry.model.transfer.TransferStatus;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
@ -303,6 +304,27 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable,
|
||||||
return thisCastToDerived();
|
return thisCastToDerived();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a pending transfer.
|
||||||
|
*
|
||||||
|
* <p>This removes the {@link StatusValue#PENDING_TRANSFER} status, clears all the
|
||||||
|
* server-approve fields on the {@link TransferData} including the extended registration years
|
||||||
|
* field, and sets the expiration time of the last pending transfer (i.e. the one being cleared)
|
||||||
|
* to now.
|
||||||
|
*/
|
||||||
|
public B clearPendingTransfer(TransferStatus transferStatus, DateTime now) {
|
||||||
|
removeStatusValue(StatusValue.PENDING_TRANSFER);
|
||||||
|
return setTransferData(getInstance().getTransferData().asBuilder()
|
||||||
|
.setExtendedRegistrationYears(null)
|
||||||
|
.setServerApproveEntities(null)
|
||||||
|
.setServerApproveBillingEvent(null)
|
||||||
|
.setServerApproveAutorenewEvent(null)
|
||||||
|
.setServerApproveAutorenewPollMessage(null)
|
||||||
|
.setTransferStatus(transferStatus)
|
||||||
|
.setPendingTransferExpirationTime(now)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
/** Wipe out any personal information in the resource. */
|
/** Wipe out any personal information in the resource. */
|
||||||
public B wipeOut() {
|
public B wipeOut() {
|
||||||
return thisCastToDerived();
|
return thisCastToDerived();
|
||||||
|
|
|
@ -45,7 +45,9 @@ public abstract class BaseFee extends ImmutableObject {
|
||||||
CREATE("create"),
|
CREATE("create"),
|
||||||
EAP("Early Access Period, fee expires: %s"),
|
EAP("Early Access Period, fee expires: %s"),
|
||||||
RENEW("renew"),
|
RENEW("renew"),
|
||||||
RESTORE("restore");
|
RESTORE("restore"),
|
||||||
|
UPDATE("update"),
|
||||||
|
CREDIT("%s credit");
|
||||||
|
|
||||||
private final String formatString;
|
private final String formatString;
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,12 @@ import java.math.BigDecimal;
|
||||||
|
|
||||||
/** A credit, in currency units specified elsewhere in the xml, and with an optional description. */
|
/** A credit, in currency units specified elsewhere in the xml, and with an optional description. */
|
||||||
public class Credit extends BaseFee {
|
public class Credit extends BaseFee {
|
||||||
public static Credit create(BigDecimal cost, String description) {
|
public static Credit create(BigDecimal cost, FeeType type, Object... descriptionArgs) {
|
||||||
Credit instance = new Credit();
|
Credit instance = new Credit();
|
||||||
instance.cost = checkNotNull(cost);
|
instance.cost = checkNotNull(cost);
|
||||||
checkArgument(instance.cost.signum() < 0);
|
checkArgument(instance.cost.signum() < 0);
|
||||||
instance.description = description;
|
instance.type = checkNotNull(type);
|
||||||
|
instance.generateDescription(descriptionArgs);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,12 @@ public interface FeeCheckCommandExtension<
|
||||||
R extends FeeCheckResponseExtension<?>>
|
R extends FeeCheckResponseExtension<?>>
|
||||||
extends CommandExtension {
|
extends CommandExtension {
|
||||||
|
|
||||||
/** True if this version of the fee extension specifies the currency at the top level. */
|
/**
|
||||||
public boolean isCurrencySupported();
|
* Three-character ISO4217 currency code.
|
||||||
|
*
|
||||||
/** Three-character currency code; throws an exception if currency is not supported. */
|
* <p>Returns null if this version of the fee extension doesn't specify currency at the top level.
|
||||||
public CurrencyUnit getCurrency() throws UnsupportedOperationException;
|
*/
|
||||||
|
public CurrencyUnit getCurrency();
|
||||||
|
|
||||||
public ImmutableSet<C> getItems();
|
public ImmutableSet<C> getItems();
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,12 @@ public interface FeeQueryCommandExtensionItem {
|
||||||
UPDATE
|
UPDATE
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True if this version of fee extension includes a currency in this type of query item. */
|
/**
|
||||||
public boolean isCurrencySupported();
|
* Three-character ISO4217 currency code.
|
||||||
|
*
|
||||||
/** A three-character ISO4217 currency code; throws an exception if currency is not supported. */
|
* <p>Returns null if this version of the fee extension doesn't specify currency at the top level.
|
||||||
public CurrencyUnit getCurrency() throws UnsupportedOperationException;
|
*/
|
||||||
|
public CurrencyUnit getCurrency();
|
||||||
|
|
||||||
/** The name of the command being checked. */
|
/** The name of the command being checked. */
|
||||||
public CommandName getCommandName();
|
public CommandName getCommandName();
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
package google.registry.model.domain.fee;
|
package google.registry.model.domain.fee;
|
||||||
|
|
||||||
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
|
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
@ -51,6 +53,6 @@ public abstract class FeeTransformCommandExtensionImpl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Credit> getCredits() {
|
public List<Credit> getCredits() {
|
||||||
return credits;
|
return nullToEmpty(credits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
|
|
||||||
package google.registry.model.domain.fee;
|
package google.registry.model.domain.fee;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||||
|
import java.util.List;
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
|
|
||||||
/** Interface for fee extensions in Create, Renew, Transfer and Update responses. */
|
/** Interface for fee extensions in Create, Renew, Transfer and Update responses. */
|
||||||
|
@ -24,8 +24,8 @@ public interface FeeTransformResponseExtension extends ResponseExtension {
|
||||||
/** Builder for {@link FeeTransformResponseExtension}. */
|
/** Builder for {@link FeeTransformResponseExtension}. */
|
||||||
public interface Builder {
|
public interface Builder {
|
||||||
Builder setCurrency(CurrencyUnit currency);
|
Builder setCurrency(CurrencyUnit currency);
|
||||||
Builder setFees(ImmutableList<Fee> fees);
|
Builder setFees(List<Fee> fees);
|
||||||
Builder setCredits(ImmutableList<Credit> credits);
|
Builder setCredits(List<Credit> credits);
|
||||||
FeeTransformResponseExtension build();
|
FeeTransformResponseExtension build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
|
|
||||||
package google.registry.model.domain.fee;
|
package google.registry.model.domain.fee;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||||
|
|
||||||
import google.registry.model.Buildable.GenericBuilder;
|
import google.registry.model.Buildable.GenericBuilder;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -53,14 +54,14 @@ public class FeeTransformResponseExtensionImpl extends ImmutableObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public B setFees(ImmutableList<Fee> fees) {
|
public B setFees(List<Fee> fees) {
|
||||||
getInstance().fees = fees;
|
getInstance().fees = fees;
|
||||||
return thisCastToDerived();
|
return thisCastToDerived();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public B setCredits(ImmutableList<Credit> credits) {
|
public B setCredits(List<Credit> credits) {
|
||||||
getInstance().credits = credits;
|
getInstance().credits = forceEmptyToNull(credits);
|
||||||
return thisCastToDerived();
|
return thisCastToDerived();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
package google.registry.model.domain.fee;
|
package google.registry.model.domain.fee;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.model.Buildable.GenericBuilder;
|
import google.registry.model.Buildable.GenericBuilder;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -54,13 +53,13 @@ public class FeeTransformResponseExtensionImplNoCredits extends ImmutableObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public B setFees(ImmutableList<Fee> fees) {
|
public B setFees(List<Fee> fees) {
|
||||||
getInstance().fees = fees;
|
getInstance().fees = fees;
|
||||||
return thisCastToDerived();
|
return thisCastToDerived();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public B setCredits(ImmutableList<Credit> credits) {
|
public B setCredits(List<Credit> credits) {
|
||||||
return thisCastToDerived();
|
return thisCastToDerived();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,6 @@ public class FeeCheckCommandExtensionItemV06
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
return currency;
|
return currency;
|
||||||
|
|
|
@ -36,14 +36,9 @@ public class FeeCheckCommandExtensionV06 extends ImmutableObject
|
||||||
@XmlElement(name = "domain")
|
@XmlElement(name = "domain")
|
||||||
Set<FeeCheckCommandExtensionItemV06> items;
|
Set<FeeCheckCommandExtensionItemV06> items;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
throw new UnsupportedOperationException("Currency not supported");
|
return null; // This version of the fee extension doesn't specify a top-level currency.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -29,11 +29,6 @@ public class FeeInfoCommandExtensionV06
|
||||||
/** A three-character ISO4217 currency code. */
|
/** A three-character ISO4217 currency code. */
|
||||||
CurrencyUnit currency;
|
CurrencyUnit currency;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
return currency;
|
return currency;
|
||||||
|
|
|
@ -60,14 +60,12 @@ public class FeeCheckCommandExtensionV11 extends ImmutableObject
|
||||||
@XmlElement(name = "class")
|
@XmlElement(name = "class")
|
||||||
String feeClass;
|
String feeClass;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
throw new UnsupportedOperationException("Currency not supported");
|
// This version of the fee extension does not have any items, and although the currency is
|
||||||
|
// specified at the top level we've modeled it as a single fake item with the currency inside,
|
||||||
|
// so there's no top level currency to return here.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -130,11 +128,6 @@ public class FeeCheckCommandExtensionV11 extends ImmutableObject
|
||||||
throw new UnsupportedOperationException("Domain not supported");
|
throw new UnsupportedOperationException("Domain not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
return currency;
|
return currency;
|
||||||
|
|
|
@ -76,14 +76,9 @@ public class FeeCheckCommandExtensionItemV12
|
||||||
throw new UnsupportedOperationException("Domain not supported");
|
throw new UnsupportedOperationException("Domain not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
throw new UnsupportedOperationException("Currency not supported");
|
return null; // This version of the fee extension doesn't specify currency per-item.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -37,11 +37,6 @@ public class FeeCheckCommandExtensionV12 extends ImmutableObject
|
||||||
|
|
||||||
CurrencyUnit currency;
|
CurrencyUnit currency;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrencySupported() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrencyUnit getCurrency() {
|
public CurrencyUnit getCurrency() {
|
||||||
return currency;
|
return currency;
|
||||||
|
|
|
@ -30,4 +30,8 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||||
public class FlagsCreateCommandExtension implements CommandExtension {
|
public class FlagsCreateCommandExtension implements CommandExtension {
|
||||||
@XmlElement(name = "flag")
|
@XmlElement(name = "flag")
|
||||||
List<String> flags;
|
List<String> flags;
|
||||||
|
|
||||||
|
public List<String> getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,4 +30,12 @@ import javax.xml.bind.annotation.XmlType;
|
||||||
public class FlagsTransferCommandExtension implements CommandExtension {
|
public class FlagsTransferCommandExtension implements CommandExtension {
|
||||||
FlagsList add; // list of flags to be added (turned on)
|
FlagsList add; // list of flags to be added (turned on)
|
||||||
FlagsList rem; // list of flags to be removed (turned off)
|
FlagsList rem; // list of flags to be removed (turned off)
|
||||||
|
|
||||||
|
public FlagsList getAddFlags() {
|
||||||
|
return add;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlagsList getRemoveFlags() {
|
||||||
|
return rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,4 +50,8 @@ public enum TransferStatus {
|
||||||
public String getXmlName() {
|
public String getXmlName() {
|
||||||
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, toString());
|
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isApproved() {
|
||||||
|
return this.equals(CLIENT_APPROVED) || this.equals(SERVER_APPROVED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ import google.registry.gcs.GcsServiceModule;
|
||||||
import google.registry.groups.DirectoryModule;
|
import google.registry.groups.DirectoryModule;
|
||||||
import google.registry.groups.GroupsModule;
|
import google.registry.groups.GroupsModule;
|
||||||
import google.registry.groups.GroupssettingsModule;
|
import google.registry.groups.GroupssettingsModule;
|
||||||
import google.registry.keyring.api.KeyModule;
|
|
||||||
import google.registry.keyring.api.DummyKeyringModule;
|
import google.registry.keyring.api.DummyKeyringModule;
|
||||||
|
import google.registry.keyring.api.KeyModule;
|
||||||
import google.registry.monitoring.metrics.MetricReporter;
|
import google.registry.monitoring.metrics.MetricReporter;
|
||||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||||
import google.registry.rde.JSchModule;
|
import google.registry.rde.JSchModule;
|
||||||
|
@ -52,6 +52,7 @@ import javax.inject.Singleton;
|
||||||
DatastoreServiceModule.class,
|
DatastoreServiceModule.class,
|
||||||
DirectoryModule.class,
|
DirectoryModule.class,
|
||||||
DriveModule.class,
|
DriveModule.class,
|
||||||
|
DummyKeyringModule.class,
|
||||||
GcsServiceModule.class,
|
GcsServiceModule.class,
|
||||||
GoogleCredentialModule.class,
|
GoogleCredentialModule.class,
|
||||||
GroupsModule.class,
|
GroupsModule.class,
|
||||||
|
@ -68,7 +69,6 @@ import javax.inject.Singleton;
|
||||||
UrlFetchTransportModule.class,
|
UrlFetchTransportModule.class,
|
||||||
UseAppIdentityCredentialForGoogleApisModule.class,
|
UseAppIdentityCredentialForGoogleApisModule.class,
|
||||||
VoidDnsWriterModule.class,
|
VoidDnsWriterModule.class,
|
||||||
DummyKeyringModule.class,
|
|
||||||
})
|
})
|
||||||
interface BackendComponent {
|
interface BackendComponent {
|
||||||
BackendRequestComponent startRequest(RequestModule requestModule);
|
BackendRequestComponent startRequest(RequestModule requestModule);
|
||||||
|
|
|
@ -17,8 +17,8 @@ package google.registry.module.frontend;
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import google.registry.braintree.BraintreeModule;
|
import google.registry.braintree.BraintreeModule;
|
||||||
import google.registry.config.ConfigModule;
|
import google.registry.config.ConfigModule;
|
||||||
import google.registry.keyring.api.KeyModule;
|
|
||||||
import google.registry.keyring.api.DummyKeyringModule;
|
import google.registry.keyring.api.DummyKeyringModule;
|
||||||
|
import google.registry.keyring.api.KeyModule;
|
||||||
import google.registry.monitoring.metrics.MetricReporter;
|
import google.registry.monitoring.metrics.MetricReporter;
|
||||||
import google.registry.monitoring.whitebox.StackdriverModule;
|
import google.registry.monitoring.whitebox.StackdriverModule;
|
||||||
import google.registry.request.Modules.AppIdentityCredentialModule;
|
import google.registry.request.Modules.AppIdentityCredentialModule;
|
||||||
|
@ -40,6 +40,7 @@ import javax.inject.Singleton;
|
||||||
BraintreeModule.class,
|
BraintreeModule.class,
|
||||||
ConfigModule.class,
|
ConfigModule.class,
|
||||||
ConsoleConfigModule.class,
|
ConsoleConfigModule.class,
|
||||||
|
DummyKeyringModule.class,
|
||||||
FrontendMetricsModule.class,
|
FrontendMetricsModule.class,
|
||||||
Jackson2Module.class,
|
Jackson2Module.class,
|
||||||
KeyModule.class,
|
KeyModule.class,
|
||||||
|
@ -49,7 +50,6 @@ import javax.inject.Singleton;
|
||||||
UrlFetchTransportModule.class,
|
UrlFetchTransportModule.class,
|
||||||
UseAppIdentityCredentialForGoogleApisModule.class,
|
UseAppIdentityCredentialForGoogleApisModule.class,
|
||||||
UserServiceModule.class,
|
UserServiceModule.class,
|
||||||
DummyKeyringModule.class,
|
|
||||||
})
|
})
|
||||||
interface FrontendComponent {
|
interface FrontendComponent {
|
||||||
FrontendRequestComponent startRequest(RequestModule requestModule);
|
FrontendRequestComponent startRequest(RequestModule requestModule);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import google.registry.flows.EppConsoleAction;
|
||||||
import google.registry.flows.EppTlsAction;
|
import google.registry.flows.EppTlsAction;
|
||||||
import google.registry.flows.FlowComponent;
|
import google.registry.flows.FlowComponent;
|
||||||
import google.registry.flows.TlsCredentials.EppTlsModule;
|
import google.registry.flows.TlsCredentials.EppTlsModule;
|
||||||
|
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||||
import google.registry.rdap.RdapAutnumAction;
|
import google.registry.rdap.RdapAutnumAction;
|
||||||
import google.registry.rdap.RdapDomainAction;
|
import google.registry.rdap.RdapDomainAction;
|
||||||
import google.registry.rdap.RdapDomainSearchAction;
|
import google.registry.rdap.RdapDomainSearchAction;
|
||||||
|
@ -50,6 +51,7 @@ import google.registry.whois.WhoisServer;
|
||||||
RdapModule.class,
|
RdapModule.class,
|
||||||
RegistrarUserModule.class,
|
RegistrarUserModule.class,
|
||||||
RequestModule.class,
|
RequestModule.class,
|
||||||
|
WhiteboxModule.class,
|
||||||
WhoisModule.class,
|
WhoisModule.class,
|
||||||
})
|
})
|
||||||
interface FrontendRequestComponent {
|
interface FrontendRequestComponent {
|
||||||
|
|
|
@ -24,6 +24,7 @@ java_library(
|
||||||
"//java/google/registry/keyring/api",
|
"//java/google/registry/keyring/api",
|
||||||
"//java/google/registry/loadtest",
|
"//java/google/registry/loadtest",
|
||||||
"//java/google/registry/mapreduce",
|
"//java/google/registry/mapreduce",
|
||||||
|
"//java/google/registry/monitoring/whitebox",
|
||||||
"//java/google/registry/request",
|
"//java/google/registry/request",
|
||||||
"//java/google/registry/request:modules",
|
"//java/google/registry/request:modules",
|
||||||
"//java/google/registry/tools/server",
|
"//java/google/registry/tools/server",
|
||||||
|
|
|
@ -21,8 +21,8 @@ import google.registry.gcs.GcsServiceModule;
|
||||||
import google.registry.groups.DirectoryModule;
|
import google.registry.groups.DirectoryModule;
|
||||||
import google.registry.groups.GroupsModule;
|
import google.registry.groups.GroupsModule;
|
||||||
import google.registry.groups.GroupssettingsModule;
|
import google.registry.groups.GroupssettingsModule;
|
||||||
import google.registry.keyring.api.KeyModule;
|
|
||||||
import google.registry.keyring.api.DummyKeyringModule;
|
import google.registry.keyring.api.DummyKeyringModule;
|
||||||
|
import google.registry.keyring.api.KeyModule;
|
||||||
import google.registry.request.Modules.AppIdentityCredentialModule;
|
import google.registry.request.Modules.AppIdentityCredentialModule;
|
||||||
import google.registry.request.Modules.DatastoreServiceModule;
|
import google.registry.request.Modules.DatastoreServiceModule;
|
||||||
import google.registry.request.Modules.GoogleCredentialModule;
|
import google.registry.request.Modules.GoogleCredentialModule;
|
||||||
|
@ -44,6 +44,7 @@ import javax.inject.Singleton;
|
||||||
DatastoreServiceModule.class,
|
DatastoreServiceModule.class,
|
||||||
DirectoryModule.class,
|
DirectoryModule.class,
|
||||||
DriveModule.class,
|
DriveModule.class,
|
||||||
|
DummyKeyringModule.class,
|
||||||
GcsServiceModule.class,
|
GcsServiceModule.class,
|
||||||
GoogleCredentialModule.class,
|
GoogleCredentialModule.class,
|
||||||
GroupsModule.class,
|
GroupsModule.class,
|
||||||
|
@ -55,7 +56,6 @@ import javax.inject.Singleton;
|
||||||
UseAppIdentityCredentialForGoogleApisModule.class,
|
UseAppIdentityCredentialForGoogleApisModule.class,
|
||||||
SystemClockModule.class,
|
SystemClockModule.class,
|
||||||
SystemSleeperModule.class,
|
SystemSleeperModule.class,
|
||||||
DummyKeyringModule.class,
|
|
||||||
})
|
})
|
||||||
interface ToolsComponent {
|
interface ToolsComponent {
|
||||||
ToolsRequestComponent startRequest(RequestModule requestModule);
|
ToolsRequestComponent startRequest(RequestModule requestModule);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import google.registry.flows.FlowComponent;
|
||||||
import google.registry.loadtest.LoadTestAction;
|
import google.registry.loadtest.LoadTestAction;
|
||||||
import google.registry.loadtest.LoadTestModule;
|
import google.registry.loadtest.LoadTestModule;
|
||||||
import google.registry.mapreduce.MapreduceModule;
|
import google.registry.mapreduce.MapreduceModule;
|
||||||
|
import google.registry.monitoring.whitebox.WhiteboxModule;
|
||||||
import google.registry.request.RequestModule;
|
import google.registry.request.RequestModule;
|
||||||
import google.registry.request.RequestScope;
|
import google.registry.request.RequestScope;
|
||||||
import google.registry.tools.server.CreateGroupsAction;
|
import google.registry.tools.server.CreateGroupsAction;
|
||||||
|
@ -54,6 +55,7 @@ import google.registry.tools.server.javascrap.RefreshAllDomainsAction;
|
||||||
MapreduceModule.class,
|
MapreduceModule.class,
|
||||||
RequestModule.class,
|
RequestModule.class,
|
||||||
ToolsServerModule.class,
|
ToolsServerModule.class,
|
||||||
|
WhiteboxModule.class,
|
||||||
})
|
})
|
||||||
interface ToolsRequestComponent {
|
interface ToolsRequestComponent {
|
||||||
BackfillAutorenewBillingFlagAction backfillAutorenewBillingFlagAction();
|
BackfillAutorenewBillingFlagAction backfillAutorenewBillingFlagAction();
|
||||||
|
|
|
@ -20,11 +20,11 @@ import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withUrl;
|
||||||
import com.google.appengine.api.modules.ModulesService;
|
import com.google.appengine.api.modules.ModulesService;
|
||||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
import com.google.appengine.api.taskqueue.TransientFailureException;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.base.Supplier;
|
||||||
import google.registry.util.FormattingLogger;
|
import google.registry.util.FormattingLogger;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.UUID;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collector of metric information. Enqueues collected metrics to a task queue to be written to
|
* A collector of metric information. Enqueues collected metrics to a task queue to be written to
|
||||||
|
@ -39,18 +39,17 @@ public class BigQueryMetricsEnqueuer {
|
||||||
public static final String QUEUE = "bigquery-streaming-metrics";
|
public static final String QUEUE = "bigquery-streaming-metrics";
|
||||||
|
|
||||||
@Inject ModulesService modulesService;
|
@Inject ModulesService modulesService;
|
||||||
|
@Inject @Named("insertIdGenerator") Supplier<String> idGenerator;
|
||||||
|
|
||||||
@Inject
|
@Inject BigQueryMetricsEnqueuer() {}
|
||||||
BigQueryMetricsEnqueuer() {}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
public void export(BigQueryMetric metric) {
|
||||||
void export(BigQueryMetric metric, String insertId) {
|
|
||||||
try {
|
try {
|
||||||
String hostname = modulesService.getVersionHostname("backend", null);
|
String hostname = modulesService.getVersionHostname("backend", null);
|
||||||
TaskOptions opts =
|
TaskOptions opts =
|
||||||
withUrl(MetricsExportAction.PATH)
|
withUrl(MetricsExportAction.PATH)
|
||||||
.header("Host", hostname)
|
.header("Host", hostname)
|
||||||
.param("insertId", insertId);
|
.param("insertId", idGenerator.get());
|
||||||
for (Entry<String, String> entry : metric.getBigQueryRowEncoding().entrySet()) {
|
for (Entry<String, String> entry : metric.getBigQueryRowEncoding().entrySet()) {
|
||||||
opts.param(entry.getKey(), entry.getValue());
|
opts.param(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
@ -61,9 +60,4 @@ public class BigQueryMetricsEnqueuer {
|
||||||
logger.info(e, e.getMessage());
|
logger.info(e, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Enqueue a metric to be exported to BigQuery. */
|
|
||||||
public void export(BigQueryMetric metric) {
|
|
||||||
export(metric, UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue