Ver código fonte

allow creating bubble without configuring anything

tags/v0.17.0
Jonathan Cobb 4 anos atrás
pai
commit
067c96ec8f
14 arquivos alterados com 168 adições e 40 exclusões
  1. +1
    -0
      bubble-server/src/main/java/bubble/ApiConstants.java
  2. +9
    -0
      bubble-server/src/main/java/bubble/dao/bill/AccountPaymentMethodDAO.java
  3. +3
    -3
      bubble-server/src/main/java/bubble/model/bill/AccountPlan.java
  4. +2
    -1
      bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java
  5. +6
    -1
      bubble-server/src/main/java/bubble/resources/bill/AccountPaymentMethodsResource.java
  6. +58
    -29
      bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java
  7. +3
    -1
      bubble-server/src/main/java/bubble/service/cloud/NodeLauncher.java
  8. +1
    -1
      bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java
  9. +1
    -1
      bubble-server/src/main/resources/messages
  10. +6
    -0
      bubble-server/src/test/java/bubble/test/ActivatedBubbleModelTestBase.java
  11. +1
    -3
      bubble-server/src/test/java/bubble/test/payment/PaymentTestBase.java
  12. +1
    -0
      bubble-server/src/test/java/bubble/test/system/NetworkTest.java
  13. +7
    -0
      bubble-server/src/test/java/bubble/test/system/NetworkTestBase.java
  14. +69
    -0
      bubble-server/src/test/resources/models/tests/network/minimal_launch.json

+ 1
- 0
bubble-server/src/main/java/bubble/ApiConstants.java Ver arquivo

@@ -278,6 +278,7 @@ public class ApiConstants {
public static String getReferer(ContainerRequest ctx) { return ctx.getHeaderString(REFERER); }

public static final String DETECT_LOCALE = "detect";
public static final String DETECT_TIMEZONE = "detect";

public static List<String> getLocales(ContainerRequest ctx, String defaultLocale) {
final List<String> locales = new ArrayList<>();


+ 9
- 0
bubble-server/src/main/java/bubble/dao/bill/AccountPaymentMethodDAO.java Ver arquivo

@@ -21,6 +21,7 @@ import java.util.List;

import static org.cobbzilla.util.daemon.ZillaRuntime.now;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
import static org.hibernate.criterion.Restrictions.*;

@Repository @Slf4j
public class AccountPaymentMethodDAO extends AccountOwnedEntityDAO<AccountPaymentMethod> {
@@ -42,6 +43,14 @@ public class AccountPaymentMethodDAO extends AccountOwnedEntityDAO<AccountPaymen
return findByFields("account", account, "paymentMethodType", PaymentMethodType.promotional_credit, "deleted", null);
}

public List<AccountPaymentMethod> findByAccountAndNotPromoAndNotDeleted(String account) {
return list(criteria().add(and(
eq("account", account),
ne("paymentMethodType", PaymentMethodType.promotional_credit),
isNull("deleted")
)));
}

public List<AccountPaymentMethod> findByAccountAndCloud(String accountUuid, String cloud) {
return findByFields("account", accountUuid, "cloud", cloud);
}


+ 3
- 3
bubble-server/src/main/java/bubble/model/bill/AccountPlan.java Ver arquivo

@@ -163,16 +163,16 @@ public class AccountPlan extends IdentifiableBase implements HasNetwork {
public boolean hasForkHost () { return !empty(forkHost); }

@Transient @Getter @Setter private transient Boolean syncPassword = null;
public boolean syncPassword () { return bool(syncPassword); }
public boolean syncPassword () { return syncPassword == null || syncPassword; }

@Transient @Getter @Setter private Boolean launchLock;
public boolean launchLock() { return bool(launchLock); }

@Transient @Getter @Setter private transient Boolean sendErrors = null;
public boolean sendErrors () { return bool(sendErrors); }
public boolean sendErrors () { return sendErrors == null || sendErrors; }

@Transient @Getter @Setter private transient Boolean sendMetrics = null;
public boolean sendMetrics () { return bool(sendMetrics); }
public boolean sendMetrics () { return sendMetrics == null || sendMetrics; }

public BubbleNetwork bubbleNetwork(Account account,
BubbleDomain domain,


+ 2
- 1
bubble-server/src/main/java/bubble/resources/account/AccountOwnedResource.java Ver arquivo

@@ -151,13 +151,14 @@ public class AccountOwnedResource<E extends HasAccount, DAO extends AccountOwned

if (!canCreate(req, ctx, caller, request)) return invalid("err.cannotCreate", "Create entity not allowed", request.getName());

final E toCreate = setReferences(ctx, caller, instantiate(getEntityClass(), request).setAccount(getAccountUuid(ctx)));
final E toCreate = setReferences(ctx, req, caller, instantiate(getEntityClass(), request).setAccount(getAccountUuid(ctx)));
return ok(daoCreate(toCreate));
}

protected Object daoCreate(E toCreate) { return getDao().create(toCreate); }

protected E setReferences(ContainerRequest ctx, Account caller, E e) { return e; }
protected E setReferences(ContainerRequest ctx, Request req, Account caller, E e) { return setReferences(ctx, caller, e); }

@POST @Path("/{id}")
public Response update(@Context ContainerRequest ctx,


+ 6
- 1
bubble-server/src/main/java/bubble/resources/bill/AccountPaymentMethodsResource.java Ver arquivo

@@ -6,9 +6,11 @@ package bubble.resources.bill;

import bubble.dao.bill.AccountPaymentMethodDAO;
import bubble.dao.bill.AccountPlanDAO;
import bubble.dao.bill.BubblePlanDAO;
import bubble.model.account.Account;
import bubble.model.bill.AccountPaymentMethod;
import bubble.model.bill.AccountPlan;
import bubble.model.bill.BubblePlan;
import bubble.resources.account.AccountOwnedResource;
import bubble.server.BubbleConfiguration;
import lombok.extern.slf4j.Slf4j;
@@ -33,6 +35,7 @@ public class AccountPaymentMethodsResource extends AccountOwnedResource<AccountP

public static final String PARAM_ALL = "all";

@Autowired private BubblePlanDAO planDAO;
@Autowired private AccountPlanDAO accountPlanDAO;
@Autowired private BubbleConfiguration configuration;

@@ -78,7 +81,9 @@ public class AccountPaymentMethodsResource extends AccountOwnedResource<AccountP
@Override protected Object daoCreate(AccountPaymentMethod apm) {
if (apm.hasPreferredPlan()) {
final Account account = accountDAO.findByUuid(apm.getAccount());
accountDAO.update(account.setPreferredPlan(apm.getPreferredPlan()));
final BubblePlan plan = planDAO.findById(apm.getPreferredPlan());
if (plan == null) throw invalidEx("err.plan.notFound", "plan not found: "+apm.getPreferredPlan());
accountDAO.update(account.setPreferredPlan(plan.getUuid()));
}
return super.daoCreate(apm);
}


+ 58
- 29
bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java Ver arquivo

@@ -101,11 +101,18 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
return super.canDelete(ctx, caller, found);
}

@Override protected AccountPlan setReferences(ContainerRequest ctx, Account caller, AccountPlan request) {
@Override protected AccountPlan setReferences(ContainerRequest ctx, Request req, Account caller, AccountPlan request) {

// ensure we have latest account settings (preferredPlan/etc)
caller = accountDAO.findByUuid(caller.getUuid());

final ValidationResult errors = new ValidationResult();
if (!request.hasTimezone()) errors.addViolation("err.timezone.required");
if (!request.hasLocale()) errors.addViolation("err.locale.required");
if (!request.hasTimezone() || request.getTimezone().equals(DETECT_TIMEZONE)) {
request.setTimezone(geoService.getTimeZone(caller, getRemoteHost(req)).getStandardName());
}
if (!request.hasLocale() || request.getLocale().equals(DETECT_LOCALE)) {
request.setLocale(geoService.getFirstLocale(account, getRemoteHost(req), normalizeLangHeader(req)));
}
request.setAccount(caller.getUuid());

if (request.hasSshKey()) {
@@ -115,17 +122,25 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
} else {
request.setSshKey(sshKey.getUuid());
}
} else if (configuration.isSageLauncher()) {
final List<AccountSshKey> sshKeys = sshKeyDAO.findByAccount(caller.getUuid());
if (empty(sshKeys)) {
request.setSshKey(null);
} else {
request.setSshKey(sshKeys.get(0).getUuid());
}
} else {
request.setSshKey(null); // if it's an empty string, make it null (see simple_network test)
}

final BubbleDomain domain = domainDAO.findByAccountAndId(caller.getUuid(), request.getDomain());
BubbleDomain domain = domainDAO.findByAccountAndId(caller.getUuid(), request.getDomain());
if (domain == null) {
log.info("setReferences: domain not found: "+request.getDomain()+" for caller: "+caller.getUuid());
errors.addViolation("err.domain.required");
final List<BubbleDomain> domains = domainDAO.findByAccount(caller.getUuid());
if (empty(domains)) return die("setReferences: no domains found for account: "+caller.getUuid());
domain = domains.get(0);
request.setDomain(domain.getUuid());
} else {
request.setDomain(domain.getUuid());

final BubbleNetwork existingNetwork = networkDAO.findByNameAndDomainName(request.getName(), domain.getName());
if (existingNetwork != null) errors.addViolation("err.name.networkNameAlreadyExists");
}
@@ -138,7 +153,15 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
if (!validateRegexMatches(HOST_PATTERN, forkHost)) {
errors.addViolation("err.forkHost.invalid");
} else if (domain != null && !forkHost.endsWith("."+domain.getName())) {
errors.addViolation("err.forkHost.domainMismatch");
final BubbleDomain foundDomain = domainDAO.findByAccount(caller.getUuid()).stream()
.filter(d -> forkHost.equals("." + d.getName()))
.findFirst().orElse(null);
if (foundDomain == null) {
errors.addViolation("err.forkHost.domain.notFound");
} else {
request.setDomain(foundDomain.getUuid());
}

} else if (domain != null) {
request.setName(domain.networkFromFqdn(forkHost, errors));
validateName(request, errors);
@@ -146,11 +169,8 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
}
} else {
if (!request.hasNickname()) {
if (request.hasName()) {
request.setNickname(request.getName());
} else {
errors.addViolation("err.name.required");
}
if (!request.hasName()) request.setName(newNetworkName());
request.setNickname(request.getName());
}
if (request.hasNickname() && request.getNickname().length() > NAME_MAXLEN) {
errors.addViolation("err.name.tooLong");
@@ -160,9 +180,14 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
}
log.info("setReferences: after calling validateName, request.name="+request.getName());

final BubblePlan plan = planDAO.findByAccountOrParentAndId(caller, request.getPlan());
BubblePlan plan = planDAO.findByAccountOrParentAndId(caller, request.getPlan());
if (plan == null) {
errors.addViolation("err.plan.required");
plan = planDAO.findByUuid(caller.getPreferredPlan());
if (plan == null) {
errors.addViolation("err.plan.required");
} else {
request.setPlan(plan.getUuid());
}
} else {
request.setPlan(plan.getUuid());
}
@@ -189,22 +214,26 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
AccountPaymentMethod paymentMethod = null;
if (configuration.paymentsEnabled()) {
if (!request.hasPaymentMethodObject()) {
errors.addViolation("err.paymentMethod.required");
} else {
if (request.getPaymentMethodObject().hasUuid()) {
paymentMethod = paymentMethodDAO.findByUuid(request.getPaymentMethodObject().getUuid());
if (paymentMethod == null) errors.addViolation("err.purchase.paymentMethodNotFound");
final List<AccountPaymentMethod> paymentMethods = paymentMethodDAO.findByAccountAndNotPromoAndNotDeleted(caller.getUuid());
if (empty(paymentMethods)) {
errors.addViolation("err.paymentMethod.required");
} else {
paymentMethod = request.getPaymentMethodObject();
request.setPaymentMethodObject(paymentMethods.get(0));
}
if (paymentMethod != null && plan != null) {
if (paymentMethod.hasPromotion() || paymentMethod.getPaymentMethodType() == PaymentMethodType.promotional_credit) {
// cannot pay with a promo credit, must supply another payment method.
// promos will be applied at purchase, and may result in no charge to this payment method
errors.addViolation("err.purchase.paymentMethodNotFound");
} else {
paymentMethod.setAccount(caller.getUuid()).validate(errors, configuration);
}
}
if (request.getPaymentMethodObject().hasUuid()) {
paymentMethod = paymentMethodDAO.findByUuid(request.getPaymentMethodObject().getUuid());
if (paymentMethod == null) errors.addViolation("err.purchase.paymentMethodNotFound");
} else {
paymentMethod = request.getPaymentMethodObject();
}
if (paymentMethod != null && plan != null) {
if (paymentMethod.hasPromotion() || paymentMethod.getPaymentMethodType() == PaymentMethodType.promotional_credit) {
// cannot pay with a promo credit, must supply another payment method.
// promos will be applied at purchase, and may result in no charge to this payment method
errors.addViolation("err.purchase.paymentMethodNotFound");
} else {
paymentMethod.setAccount(caller.getUuid()).validate(errors, configuration);
}
}
}


+ 3
- 1
bubble-server/src/main/java/bubble/service/cloud/NodeLauncher.java Ver arquivo

@@ -7,6 +7,7 @@ package bubble.service.cloud;
import bubble.model.cloud.BubbleNetwork;
import bubble.model.cloud.BubbleNode;
import bubble.notify.NewNodeNotification;
import bubble.server.BubbleConfiguration;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@@ -25,6 +26,7 @@ public class NodeLauncher implements Runnable {
private final AtomicReference<String> lock;
private final StandardNetworkService networkService;
private final NodeLaunchMonitor launchMonitor;
private final BubbleConfiguration configuration;

@Override public void run() {
final String networkUuid = newNodeRequest.getNetwork();
@@ -98,7 +100,7 @@ public class NodeLauncher implements Runnable {
die("NodeLauncher.run: unknown launch exception (type="+launchException.getType()+"): "+shortError(launchException), launchException);
}
} else {
die("NodeLauncher.run: fatal launch exception: " + shortError(exception), exception);
if (!configuration.testMode()) die("NodeLauncher.run: fatal launch exception: " + shortError(exception), exception);
}
}
if (node != null && node.isRunning()) {


+ 1
- 1
bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java Ver arquivo

@@ -817,7 +817,7 @@ public class StandardNetworkService implements NetworkService {

public void backgroundNewNode(NewNodeNotification newNodeRequest, final String existingLock) {
final AtomicReference<String> lock = new AtomicReference<>(existingLock);
daemon(new NodeLauncher(newNodeRequest, lock, this, launchMonitor));
daemon(new NodeLauncher(newNodeRequest, lock, this, launchMonitor, configuration));
}

public boolean stopNetwork(final BubbleNetwork network) {


+ 1
- 1
bubble-server/src/main/resources/messages

@@ -1 +1 @@
Subproject commit 35f36a84d84cb1f73dd8cc1f18e044657675b7f9
Subproject commit 7ddb8f71fc43a5b5ce1056944e1f1876ee586f14

+ 6
- 0
bubble-server/src/test/java/bubble/test/ActivatedBubbleModelTestBase.java Ver arquivo

@@ -67,6 +67,12 @@ public abstract class ActivatedBubbleModelTestBase extends BubbleModelTestBase {
super.beforeStart(server);
}

public void mockNetwork(RestServer<BubbleConfiguration> server) {
final BubbleConfiguration configuration = server.getConfiguration();
configuration.setSpringContextPath("classpath:/spring-mock-network.xml");
configuration.getStaticAssets().setLocalOverride(null);
}

public String getDefaultDomain() { return "example.com"; }

@Override protected String[] getSqlPostScripts() { return hasExistingDb ? null : super.getSqlPostScripts(); }


+ 1
- 3
bubble-server/src/test/java/bubble/test/payment/PaymentTestBase.java Ver arquivo

@@ -15,9 +15,7 @@ public class PaymentTestBase extends ActivatedBubbleModelTestBase {
@Override protected String getManifest() { return "manifest-test"; }

@Override public void beforeStart(RestServer<BubbleConfiguration> server) {
final BubbleConfiguration configuration = server.getConfiguration();
configuration.setSpringContextPath("classpath:/spring-mock-network.xml");
configuration.getStaticAssets().setLocalOverride(null);
mockNetwork(server);
super.beforeStart(server);
}



+ 1
- 0
bubble-server/src/test/java/bubble/test/system/NetworkTest.java Ver arquivo

@@ -12,5 +12,6 @@ public class NetworkTest extends NetworkTestBase {

@Test public void testRegions () throws Exception { modelTest("network/network_regions"); }
@Test public void testGetNetworkKeys () throws Exception { modelTest("network/network_keys"); }
@Test public void testMinimalLaunch () throws Exception { modelTest("network/minimal_launch"); }

}

+ 7
- 0
bubble-server/src/test/java/bubble/test/system/NetworkTestBase.java Ver arquivo

@@ -4,10 +4,17 @@
*/
package bubble.test.system;

import bubble.server.BubbleConfiguration;
import bubble.test.ActivatedBubbleModelTestBase;
import org.cobbzilla.wizard.server.RestServer;

public class NetworkTestBase extends ActivatedBubbleModelTestBase {

@Override protected String getManifest() { return "manifest-network"; }

@Override public void beforeStart(RestServer<BubbleConfiguration> server) {
mockNetwork(server);
super.beforeStart(server);
}

}

+ 69
- 0
bubble-server/src/test/resources/models/tests/network/minimal_launch.json Ver arquivo

@@ -0,0 +1,69 @@
[
{
"comment": "create another account and login",
"include": "new_account",
"params": {
"email": "min_launch@example.com",
"verifyEmail": true
}
},

{
"comment": "list plans",
"request": { "uri": "plans" },
"response": {
"store": "plans",
"check": [
{"condition": "json.length > 0"}
]
}
},

{
"comment": "add a payment method and preferred plan",
"before": "stripe_tokenize_card",
"request": {
"uri": "me/paymentMethods",
"method": "put",
"entity": {
"paymentMethodType": "credit",
"paymentInfo": "{{stripeToken}}",
"preferredPlan": "{{plans.[0].name}}"
}
}
},

{
"comment": "add account plan",
"request": {
"uri": "me/plans",
"method": "put",
"entity": {}
},
"response": { "store": "plan" }
},

{
"comment": "start the network. sets up the first node, which does the rest",
"request": {
"uri": "me/networks/{{ plan.name }}/actions/start",
"method": "post"
},
"response": {
"store": "<<networkVar>>"
}
},

{
"comment": "list networks, verify new network is starting",
"before": "sleep 10s",
"request": { "uri": "me/networks" },
"response": {
"check": [
{"condition": "json.length === 1"},
{"condition": "json[0].getName() === '{{plan.name}}'"},
{"condition": "json[0].getState().name() === 'starting'"}
]
}
}
]

Carregando…
Cancelar
Salvar