Skip to content

Commit

Permalink
Add RegistryLock SQL schema (#243)
Browse files Browse the repository at this point in the history
* Add RegistryLock SQL schema

* Refactor a bit

* Move registrylock -> domain

* Clearing up lock workflow

* Add more docs and remove LockStatus

* Responses to CR

* Add repoId javadoc

* Add registry lock to persistence xml file

* Quote rather than backtick

* Remove unnecessary check

* File TODO

* Remove uniqueness constraint on verification code

* Remove import

* add index

* Add to SQL generation task

* Move fields around to be the same order as Hibernate's generated sql
  • Loading branch information
gbrodman committed Sep 10, 2019
1 parent b35026b commit 401653a
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 0 deletions.
237 changes: 237 additions & 0 deletions core/src/main/java/google/registry/schema/domain/RegistryLock.java
@@ -0,0 +1,237 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package google.registry.schema.domain;

import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.util.DateTimeUtils.toJodaDateTime;
import static google.registry.util.DateTimeUtils.toZonedDateTime;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;

import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import java.time.ZonedDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import org.joda.time.DateTime;

/**
* Represents a registry lock/unlock object, meaning that the domain is locked on the registry
* level.
*
* <p>Registry locks must be requested through the registrar console by a lock-enabled contact, then
* confirmed through email within a certain length of time. Until that confirmation is processed,
* the completion time will remain null and the lock will have no effect. The same applies for
* unlock actions.
*
* <p>Note that there will be at most one row per domain with a null copmleted time -- this means
* that there is at most one pending action per domain. This is enforced at the logic level.
*
* <p>Note as well that in the case of a retry of a write after an unexpected success, the unique
* constraint on {@link #verificationCode} means that the second write will fail.
*/
@Entity
@Table(
// Unique constraint to get around Hibernate's failure to handle
// auto-increment field in composite primary key.
indexes =
@Index(
name = "idx_registry_lock_repo_id_revision_id",
columnList = "repo_id, revision_id",
unique = true))
public final class RegistryLock extends ImmutableObject implements Buildable {

/** Describes the action taken by the user. */
public enum Action {
LOCK,
UNLOCK
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "revision_id", nullable = false)
private Long revisionId;

/** EPP repo ID of the domain in question. */
@Column(name = "repo_id", nullable = false)
private String repoId;

// TODO (b/140568328): remove this when everything is in Cloud SQL and we can join on "domain"
@Column(name = "domain_name", nullable = false)
private String domainName;

/**
* The ID of the registrar that performed the action -- this may be the admin ID if this action
* was performed by a superuser.
*/
@Column(name = "registrar_id", nullable = false)
private String registrarId;

/** The POC that performed the action, or null if it was a superuser. */
@Column(name = "registrar_poc_id")
private String registrarPocId;

/**
* Lock action is immutable and describes whether the action performed was a lock or an unlock.
*/
@Enumerated(EnumType.STRING)
@Column(name = "action", nullable = false)
private Action action;

/** Creation timestamp is when the lock/unlock is first requested. */
@Column(name = "creation_timestamp", nullable = false)
private ZonedDateTime creationTimestamp;

/**
* Completion timestamp is when the user has verified the lock/unlock, when this object de facto
* becomes immutable. If this field is null, it means that the lock has not been verified yet (and
* thus not been put into effect).
*/
@Column(name = "completion_timestamp")
private ZonedDateTime completionTimestamp;

/**
* The user must provide the random verification code in order to complete the lock and move the
* status from PENDING to COMPLETED.
*/
@Column(name = "verification_code", nullable = false)
private String verificationCode;

/**
* True iff this action was taken by a superuser, in response to something like a URS request. In
* this case, the action was performed by a registry admin rather than a registrar.
*/
@Column(name = "is_superuser", nullable = false)
private boolean isSuperuser;

public String getRepoId() {
return repoId;
}

public String getDomainName() {
return domainName;
}

public String getRegistrarId() {
return registrarId;
}

public String getRegistrarPocId() {
return registrarPocId;
}

public Action getAction() {
return action;
}

public DateTime getCreationTimestamp() {
return toJodaDateTime(creationTimestamp);
}

public DateTime getCompletionTimestamp() {
return toJodaDateTime(completionTimestamp);
}

public String getVerificationCode() {
return verificationCode;
}

public boolean isSuperuser() {
return isSuperuser;
}

public Long getRevisionId() {
return revisionId;
}

@Override
public Builder asBuilder() {
return new Builder(clone(this));
}

/** Builder for {@link google.registry.schema.domain.RegistryLock}. */
public static class Builder extends Buildable.Builder<RegistryLock> {
public Builder() {}

private Builder(RegistryLock instance) {
super(instance);
}

@Override
public RegistryLock build() {
checkArgumentNotNull(getInstance().repoId, "Repo ID cannot be null");
checkArgumentNotNull(getInstance().domainName, "Domain name cannot be null");
checkArgumentNotNull(getInstance().registrarId, "Registrar ID cannot be null");
checkArgumentNotNull(getInstance().action, "Action cannot be null");
checkArgumentNotNull(getInstance().creationTimestamp, "Creation timestamp cannot be null");
checkArgumentNotNull(getInstance().verificationCode, "Verification codecannot be null");
checkArgument(
getInstance().registrarPocId != null || getInstance().isSuperuser,
"Registrar POC ID must be provided if superuser is false");
return super.build();
}

public Builder setRepoId(String repoId) {
getInstance().repoId = repoId;
return this;
}

public Builder setDomainName(String domainName) {
getInstance().domainName = domainName;
return this;
}

public Builder setRegistrarId(String registrarId) {
getInstance().registrarId = registrarId;
return this;
}

public Builder setRegistrarPocId(String registrarPocId) {
getInstance().registrarPocId = registrarPocId;
return this;
}

public Builder setAction(Action action) {
getInstance().action = action;
return this;
}

public Builder setCreationTimestamp(DateTime creationTimestamp) {
getInstance().creationTimestamp = toZonedDateTime(creationTimestamp);
return this;
}

public Builder setCompletionTimestamp(DateTime lockTimestamp) {
getInstance().completionTimestamp = toZonedDateTime(lockTimestamp);
return this;
}

public Builder setVerificationCode(String verificationCode) {
getInstance().verificationCode = verificationCode;
return this;
}

public Builder isSuperuser(boolean isSuperuser) {
getInstance().isSuperuser = isSuperuser;
return this;
}
}
}
Expand Up @@ -28,6 +28,7 @@
import google.registry.model.transfer.BaseTransferObject;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.NomulusNamingStrategy;
import google.registry.schema.domain.RegistryLock;
import google.registry.schema.tld.PremiumList;
import google.registry.schema.tmch.ClaimsList;
import java.io.IOException;
Expand Down Expand Up @@ -68,6 +69,7 @@ public class GenerateSqlSchemaCommand implements Command {
GracePeriod.class,
Period.class,
PremiumList.class,
RegistryLock.class,
TransferData.class,
Trid.class);

Expand Down
1 change: 1 addition & 0 deletions core/src/main/resources/META-INF/persistence.xml
Expand Up @@ -20,6 +20,7 @@
* Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant)
-->
<class>google.registry.model.domain.DomainBase</class>
<class>google.registry.schema.domain.RegistryLock</class>
<class>google.registry.schema.tmch.ClaimsList</class>
<class>google.registry.model.transfer.BaseTransferObject</class>
<class>google.registry.schema.tld.PremiumList</class>
Expand Down
30 changes: 30 additions & 0 deletions db/src/main/resources/sql/schema/registry_lock.sql
@@ -0,0 +1,30 @@
-- Copyright 2019 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

CREATE TABLE "RegistryLock" (
revision_id BIGSERIAL NOT NULL,
action TEXT NOT NULL,
completion_timestamp TIMESTAMPTZ,
creation_timestamp TIMESTAMPTZ NOT NULL,
domain_name TEXT NOT NULL,
is_superuser BOOLEAN NOT NULL,
registrar_id TEXT NOT NULL,
registrar_poc_id TEXT,
repo_id TEXT NOT NULL,
verification_code TEXT NOT NULL,
PRIMARY KEY (revision_id)
);

ALTER TABLE IF EXISTS "RegistryLock"
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);

0 comments on commit 401653a

Please sign in to comment.