From fb2d5793336165d3369c32d67e921cc6b505c6ac Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Sun, 15 Dec 2019 22:38:47 -0500 Subject: [PATCH] recurring billing test passes, with stop/resume after resolving nonpayment --- .../dao/account/AccountOwnedEntityDAO.java | 7 +++++- .../java/bubble/model/account/HasAccount.java | 1 + .../model/bill/AccountPaymentMethod.java | 1 + .../model/cloud/BubbleNetworkState.java | 2 ++ .../account/AccountOwnedResource.java | 5 ++++- .../bill/AccountPaymentMethodsResource.java | 4 ++-- .../cloud/NetworkActionsResource.java | 2 +- .../service/cloud/StandardNetworkService.java | 22 +++++++++++++------ .../post_auth/ResourceMessages.properties | 2 +- .../java/bubble/mock/MockNetworkService.java | 17 +++++++++----- .../tests/payment/recurring_billing.json | 3 ++- 11 files changed, 46 insertions(+), 20 deletions(-) diff --git a/bubble-server/src/main/java/bubble/dao/account/AccountOwnedEntityDAO.java b/bubble-server/src/main/java/bubble/dao/account/AccountOwnedEntityDAO.java index 5b60c129..35e34bdc 100644 --- a/bubble-server/src/main/java/bubble/dao/account/AccountOwnedEntityDAO.java +++ b/bubble-server/src/main/java/bubble/dao/account/AccountOwnedEntityDAO.java @@ -2,7 +2,9 @@ package bubble.dao.account; import bubble.model.account.Account; import bubble.model.account.HasAccount; +import bubble.model.account.HasAccountNoName; import bubble.server.BubbleConfiguration; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.dao.AbstractCRUDDAO; import org.springframework.beans.factory.annotation.Autowired; @@ -11,6 +13,7 @@ import java.io.File; import java.util.List; import static bubble.ApiConstants.HOME_DIR; +import static org.cobbzilla.util.reflect.ReflectionUtil.getFirstTypeParam; import static org.cobbzilla.util.security.ShaUtil.sha256_hex; import static org.hibernate.criterion.Restrictions.eq; import static org.hibernate.criterion.Restrictions.or; @@ -20,11 +23,13 @@ public abstract class AccountOwnedEntityDAO extends Abstra @Autowired private BubbleConfiguration configuration; + @Getter(lazy=true) private final Boolean hasNameField = !HasAccountNoName.class.isAssignableFrom(getFirstTypeParam(getClass())); + public List findByAccount(String accountUuid) { return findByField("account", accountUuid); } public E findByAccountAndId(String accountUuid, String id) { final E found = findByUniqueFields("account", accountUuid, "uuid", id); - return found != null ? found : findByUniqueFields("account", accountUuid, getNameField(), id); + return found != null || !getHasNameField() ? found : findByUniqueFields("account", accountUuid, getNameField(), id); } protected String getNameField() { return "name"; } diff --git a/bubble-server/src/main/java/bubble/model/account/HasAccount.java b/bubble-server/src/main/java/bubble/model/account/HasAccount.java index 36e8fb81..d5ece91f 100644 --- a/bubble-server/src/main/java/bubble/model/account/HasAccount.java +++ b/bubble-server/src/main/java/bubble/model/account/HasAccount.java @@ -9,5 +9,6 @@ public interface HasAccount extends Identifiable, NamedEntity { E setAccount (String account); default boolean hasAccount () { return getAccount() != null; } String getName(); + default boolean hasName() { return getName() != null; } } diff --git a/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java b/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java index 03c72346..895d85d4 100644 --- a/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java +++ b/bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java @@ -73,6 +73,7 @@ public class AccountPaymentMethod extends IdentifiableBase implements HasAccount @Column(nullable=false) @Getter @Setter private Boolean deleted = false; public boolean deleted() { return deleted != null && deleted; } + public boolean notDeleted() { return !deleted(); } public ValidationResult validate(ValidationResult result, BubbleConfiguration configuration) { diff --git a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetworkState.java b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetworkState.java index 41de9829..ebf7c559 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/BubbleNetworkState.java +++ b/bubble-server/src/main/java/bubble/model/cloud/BubbleNetworkState.java @@ -10,4 +10,6 @@ public enum BubbleNetworkState { @JsonCreator public static BubbleNetworkState fromString(String v) { return enumFromString(BubbleNetworkState.class, v); } + public boolean canStartNetwork () { return this == created || this == stopped; } + } diff --git a/bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java b/bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java index 41f4a72d..e087dbd8 100644 --- a/bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java +++ b/bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java @@ -4,6 +4,7 @@ import bubble.dao.account.AccountDAO; import bubble.dao.account.AccountOwnedEntityDAO; import bubble.model.account.Account; import bubble.model.account.HasAccount; +import bubble.model.account.HasAccountNoName; import bubble.server.BubbleConfiguration; import lombok.Getter; import org.glassfish.grizzly.http.server.Request; @@ -134,7 +135,9 @@ public class AccountOwnedResource list(ContainerRequest ctx) { - return super.list(ctx).stream().filter(p -> !p.deleted()).collect(Collectors.toList()); + return super.list(ctx).stream().filter(AccountPaymentMethod::notDeleted).collect(Collectors.toList()); } @Override protected AccountPaymentMethod setReferences(ContainerRequest ctx, Account caller, AccountPaymentMethod request) { diff --git a/bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java b/bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java index 9830031d..cf4e86d2 100644 --- a/bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java +++ b/bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java @@ -68,7 +68,7 @@ public class NetworkActionsResource { final List nodes = nodeDAO.findByNetwork(network.getUuid()); if (!nodes.isEmpty()) return invalid("err.network.alreadyStarted"); - if (network.getState() != BubbleNetworkState.created) return invalid("err.network.alreadyStarted"); + if (!network.getState().canStartNetwork()) return invalid("err.network.cannotStartInCurrentState"); return _startNetwork(network, cloud, region, req); } diff --git a/bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java b/bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java index 79bdff65..e3ddf286 100644 --- a/bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java +++ b/bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java @@ -29,6 +29,7 @@ import org.cobbzilla.util.system.CommandResult; import org.cobbzilla.util.system.CommandShell; import org.cobbzilla.wizard.cache.redis.RedisService; import org.cobbzilla.wizard.validation.MultiViolationException; +import org.cobbzilla.wizard.validation.SimpleViolationException; import org.cobbzilla.wizard.validation.ValidationResult; import org.glassfish.grizzly.http.server.Request; import org.springframework.beans.factory.annotation.Autowired; @@ -354,7 +355,7 @@ public class StandardNetworkService implements NetworkService { return node; } - String lockNetwork(String network) { + protected String lockNetwork(String network) { log.info("lockNetwork: locking "+network); final String lock = getNetworkLocks().lock(network, NET_LOCK_TIMEOUT, NET_DEADLOCK_TIMEOUT); log.info("lockNetwork: locked "+network); @@ -408,6 +409,13 @@ public class StandardNetworkService implements NetworkService { } public NewNodeNotification startNetwork(BubbleNetwork network, NetLocation netLocation) { + + if (configuration.paymentsEnabled()) { + final AccountPlan accountPlan = accountPlanDAO.findByAccountAndNetwork(network.getAccount(), network.getUuid()); + if (accountPlan == null) throw invalidEx("err.accountPlan.notFound"); + if (accountPlan.disabled()) throw invalidEx("err.accountPlan.disabled"); + } + String lock = null; try { lock = lockNetwork(network.getUuid()); @@ -416,8 +424,8 @@ public class StandardNetworkService implements NetworkService { if (!nodeDAO.findByNetwork(network.getUuid()).isEmpty()) { throw invalidEx("err.network.alreadyStarted"); } - if (network.getState() != BubbleNetworkState.created && network.getState() != BubbleNetworkState.stopped) { - throw invalidEx("err.network.cannotStartInState"); + if (!network.getState().canStartNetwork()) { + throw invalidEx("err.network.cannotStartInCurrentState"); } network.setState(BubbleNetworkState.setup); @@ -436,7 +444,7 @@ public class StandardNetworkService implements NetworkService { .setDomain(network.getDomain()) .setFork(network.fork()) .setHost(host) - .setFqdn(host+"."+network.getNetworkDomain()) + .setFqdn(host + "." + network.getNetworkDomain()) .setCloud(cloudAndRegion.getCloud().getUuid()) .setRegion(cloudAndRegion.getRegion().getInternalName()) .setLock(lock); @@ -446,6 +454,9 @@ public class StandardNetworkService implements NetworkService { return newNodeRequest; + } catch (SimpleViolationException e) { + throw e; + } catch (Exception e) { return die("startNetwork: "+e, e); } @@ -507,9 +518,6 @@ public class StandardNetworkService implements NetworkService { } public void backgroundNewNode(NewNodeNotification newNodeRequest, final String existingLock) { - final AccountPlan accountPlan = accountPlanDAO.findByAccountAndNetwork(newNodeRequest.getAccount(), newNodeRequest.getNetwork()); - if (accountPlan == null) throw invalidEx("err.accountPlan.notFound"); - if (accountPlan.disabled()) throw invalidEx("err.accountPlan.disabled"); final AtomicReference lock = new AtomicReference<>(existingLock); daemon(new NodeLauncher(newNodeRequest, lock, this)); } diff --git a/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties index 81d9b243..776e205a 100644 --- a/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties @@ -150,7 +150,7 @@ err.network.alreadyStarted=Network is already started err.network.exists=A plan already exists for this network err.networkKeys.noVerifiedContacts=No verified contacts exist err.networkName.required=Network name is required -err.network.cannotStartInState=Cannot proceed: network cannot be started in its current state +err.network.cannotStartInCurrentState=Cannot proceed: network cannot be started in its current state err.network.required=Network is required err.network.restore.nodesExist=Cannot restore when active nodes exist err.network.restore.notStopped=Cannot restore when network is running diff --git a/bubble-server/src/test/java/bubble/mock/MockNetworkService.java b/bubble-server/src/test/java/bubble/mock/MockNetworkService.java index 725b993b..09c9ae8a 100644 --- a/bubble-server/src/test/java/bubble/mock/MockNetworkService.java +++ b/bubble-server/src/test/java/bubble/mock/MockNetworkService.java @@ -9,12 +9,9 @@ import bubble.model.cloud.*; import bubble.notify.NewNodeNotification; import bubble.server.BubbleConfiguration; import bubble.service.cloud.StandardNetworkService; -import org.cobbzilla.util.system.CommandResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.io.IOException; - import static org.cobbzilla.util.daemon.ZillaRuntime.die; @Service @@ -25,9 +22,9 @@ public class MockNetworkService extends StandardNetworkService { @Autowired private BubbleNodeDAO nodeDAO; @Autowired private BubbleConfiguration configuration; - @Override public CommandResult ansibleSetup(String script) throws IOException { - return new CommandResult(0, "mock: successful", ""); - } + @Override protected String lockNetwork(String network) { return "lock"; } + @Override protected boolean confirmLock(String network, String lock) { return true; } + @Override protected void unlockNetwork(String network, String lock) {} @Override public BubbleNode newNode(NewNodeNotification nn) { @@ -66,4 +63,12 @@ public class MockNetworkService extends StandardNetworkService { return true; } + @Override public BubbleNode stopNode(BubbleNode node) { + return node.setState(BubbleNodeState.stopped); + } + + @Override public BubbleNode killNode(BubbleNode node, String message) { + return node.setState(BubbleNodeState.stopped); + } + } diff --git a/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json b/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json index 04b32554..3b4179c8 100644 --- a/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json +++ b/bubble-server/src/test/resources/models/tests/payment/recurring_billing.json @@ -476,13 +476,14 @@ }, { + "before": "unset_stripe_error", "comment": "submit payment for unpaid bill", "request": { "uri": "me/plans/{{accountPlan.uuid}}/bills/{{bills.[0].uuid}}/pay", "method": "post" }, "response": { - "check": [ {"condition": "json.getStatus.name() == 'paid'"} ] + "check": [ {"condition": "json.getStatus().name() == 'paid'"} ] } },