Sfoglia il codice sorgente

recurring billing test passes, with stop/resume after resolving nonpayment

tags/v0.1.6
Jonathan Cobb 5 anni fa
parent
commit
fb2d579333
11 ha cambiato i file con 46 aggiunte e 20 eliminazioni
  1. +6
    -1
      bubble-server/src/main/java/bubble/dao/account/AccountOwnedEntityDAO.java
  2. +1
    -0
      bubble-server/src/main/java/bubble/model/account/HasAccount.java
  3. +1
    -0
      bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java
  4. +2
    -0
      bubble-server/src/main/java/bubble/model/cloud/BubbleNetworkState.java
  5. +4
    -1
      bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java
  6. +2
    -2
      bubble-server/src/main/java/bubble/resources/bill/AccountPaymentMethodsResource.java
  7. +1
    -1
      bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java
  8. +15
    -7
      bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java
  9. +1
    -1
      bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties
  10. +11
    -6
      bubble-server/src/test/java/bubble/mock/MockNetworkService.java
  11. +2
    -1
      bubble-server/src/test/resources/models/tests/payment/recurring_billing.json

+ 6
- 1
bubble-server/src/main/java/bubble/dao/account/AccountOwnedEntityDAO.java Vedi File

@@ -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<E extends HasAccount> extends Abstra

@Autowired private BubbleConfiguration configuration;

@Getter(lazy=true) private final Boolean hasNameField = !HasAccountNoName.class.isAssignableFrom(getFirstTypeParam(getClass()));

public List<E> 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"; }


+ 1
- 0
bubble-server/src/main/java/bubble/model/account/HasAccount.java Vedi File

@@ -9,5 +9,6 @@ public interface HasAccount extends Identifiable, NamedEntity {
<E> E setAccount (String account);
default boolean hasAccount () { return getAccount() != null; }
String getName();
default boolean hasName() { return getName() != null; }

}

+ 1
- 0
bubble-server/src/main/java/bubble/model/bill/AccountPaymentMethod.java Vedi File

@@ -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) {



+ 2
- 0
bubble-server/src/main/java/bubble/model/cloud/BubbleNetworkState.java Vedi File

@@ -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; }

}

+ 4
- 1
bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java Vedi File

@@ -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<E extends HasAccount, DAO extends AccountOwned
final Account caller = checkEditable(ctx);
final E found = find(ctx, id);
if (found == null) return notFound(id);
if (!canChangeName() && !found.getName().equals(request.getName())) return notFound(id+"/"+request.getName());
if (!(found instanceof HasAccountNoName) && !canChangeName() && request.hasName() && !found.getName().equals(request.getName())) {
return notFound(id+"/"+request.getName());
}

if (!canUpdate(ctx, caller, found, request)) return invalid("err.cannotUpdate", "Update entity not allowed", request.getName());
found.update(request);


+ 2
- 2
bubble-server/src/main/java/bubble/resources/bill/AccountPaymentMethodsResource.java Vedi File

@@ -29,11 +29,11 @@ public class AccountPaymentMethodsResource extends AccountOwnedResource<AccountP

@Override protected AccountPaymentMethod find(ContainerRequest ctx, String id) {
final AccountPaymentMethod found = super.find(ctx, id);
return found.deleted() ? null : found;
return found == null || found.deleted() ? null : found;
}

@Override protected List<AccountPaymentMethod> 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) {


+ 1
- 1
bubble-server/src/main/java/bubble/resources/cloud/NetworkActionsResource.java Vedi File

@@ -68,7 +68,7 @@ public class NetworkActionsResource {
final List<BubbleNode> 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);
}


+ 15
- 7
bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java Vedi File

@@ -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<String> lock = new AtomicReference<>(existingLock);
daemon(new NodeLauncher(newNodeRequest, lock, this));
}


+ 1
- 1
bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties Vedi File

@@ -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


+ 11
- 6
bubble-server/src/test/java/bubble/mock/MockNetworkService.java Vedi File

@@ -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);
}

}

+ 2
- 1
bubble-server/src/test/resources/models/tests/payment/recurring_billing.json Vedi File

@@ -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'"} ]
}
},



Caricamento…
Annulla
Salva