@@ -9,10 +9,14 @@ import bubble.cloud.payment.promo.PromotionalPaymentDriverBase; | |||
import bubble.cloud.payment.promo.PromotionalPaymentServiceDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.bill.*; | |||
import lombok.extern.slf4j.Slf4j; | |||
import java.util.List; | |||
import java.util.Set; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||
@Slf4j | |||
public class AccountCreditPaymentDriver extends PromotionalPaymentDriverBase<AccountCreditPaymentConfig> { | |||
@Override public boolean adminAddPromoToAccount(Promotion promo, Account account) { | |||
@@ -20,7 +24,7 @@ public class AccountCreditPaymentDriver extends PromotionalPaymentDriverBase<Acc | |||
.setAccount(account.getUuid()) | |||
.setCloud(promo.getCloud()) | |||
.setPaymentMethodType(PaymentMethodType.promotional_credit) | |||
.setPaymentInfo(promo.getName()) | |||
.setPaymentInfo(promo.getName()+"_"+now()) | |||
.setMaskedPaymentInfo(promo.getName()) | |||
.setPromotion(promo.getUuid())); | |||
return true; | |||
@@ -52,12 +52,21 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> { | |||
return findByUniqueFields("account", accountUuid, "network", networkUuid); | |||
} | |||
public AccountPlan findByAccountAndNetworkAndNotDeleted(String accountUuid, String networkUuid) { | |||
return findByUniqueFields("account", accountUuid, "network", networkUuid, "deleted", null); | |||
} | |||
public AccountPlan findByNetwork(String networkUuid) { return findByUniqueField("network", networkUuid); } | |||
public List<AccountPlan> findByAccountAndNotDeleted(String account) { | |||
return findByFields("account", account, "deleted", null); | |||
} | |||
public AccountPlan findByAccountAndIdAndNotDeleted(String account, String id) { | |||
final AccountPlan accountPlan = findByUniqueFields("account", account, "uuid", id, "deleted", null); | |||
return accountPlan != null ? accountPlan : findByUniqueFields("account", account, "name", id, "deleted", null); | |||
} | |||
public List<AccountPlan> findByAccountAndPaymentMethodAndNotDeleted(String account, String paymentMethod) { | |||
return findByFields("account", account, "paymentMethod", paymentMethod, "deleted", null); | |||
} | |||
@@ -47,6 +47,16 @@ public class PromotionDAO extends AbstractCRUDDAO<Promotion> { | |||
"adminAssignOnly", false)); | |||
} | |||
public List<Promotion> findEnabledAndActiveAndLaunchFailureWithNoCode(String currency) { | |||
return filterActive(findByFields( | |||
"enabled", true, | |||
"code", null, | |||
"referral", false, | |||
"launchFailureCredit", true, | |||
"currency", currency, | |||
"adminAssignOnly", false)); | |||
} | |||
public List<Promotion> findVisibleAndEnabledAndActiveWithNoCodeOrWithCode(String code, String currency) { | |||
if (empty(code)) { | |||
return filterActive(findByFields( | |||
@@ -37,7 +37,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||
@Entity @ECType(root=true) @ECTypeCreate(method="DISABLED") | |||
@NoArgsConstructor @Accessors(chain=true) | |||
@ECIndexes({ @ECIndex(unique=true, of={"paymentMethodType", "paymentInfo"}) }) | |||
@ECIndexes({ @ECIndex(unique=true, of={"paymentMethodType", "paymentInfo"}, where="deleted IS NULL") }) | |||
@Slf4j | |||
public class AccountPaymentMethod extends IdentifiableBase implements HasAccountNoName, Scrubbable { | |||
@@ -15,6 +15,7 @@ import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import lombok.experimental.Accessors; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.collection.ArrayUtil; | |||
import org.cobbzilla.wizard.model.Identifiable; | |||
import org.cobbzilla.wizard.model.IdentifiableBase; | |||
@@ -34,9 +35,9 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||
@ECType(root=true) @ECTypeCreate(method="DISABLED") | |||
@ECTypeURIs(listFields={"account", "plan", "network", "name"}) | |||
@Entity @NoArgsConstructor @Accessors(chain=true) | |||
@Entity @NoArgsConstructor @Accessors(chain=true) @Slf4j | |||
@ECIndexes({ | |||
@ECIndex(unique=true, of={"account", "name"}), | |||
@ECIndex(unique=true, of={"account", "name"}, where="deleted IS NULL"), | |||
@ECIndex(unique=true, of={"account", "network"}), | |||
@ECIndex(unique=true, of={"plan", "network"}) | |||
}) | |||
@@ -99,22 +99,27 @@ public class Promotion extends IdentifiableBase | |||
public boolean notAdminAssignOnly() { return !adminAssignOnly(); } | |||
@ECSearchable @ECField(index=80) | |||
@ECIndex @Column(nullable=false) | |||
@Getter @Setter private Boolean launchFailureCredit = false; | |||
public boolean launchFailureCredit() { return launchFailureCredit != null && launchFailureCredit; } | |||
@ECSearchable @ECField(index=90) | |||
@ECIndex @Getter @Setter private Long validFrom; | |||
public boolean hasStarted () { return validFrom == null || validFrom > now(); } | |||
@ECSearchable @ECField(index=90) | |||
@ECSearchable @ECField(index=100) | |||
@ECIndex @Getter @Setter private Long validTo; | |||
public boolean hasEnded () { return validTo != null && validTo > now(); } | |||
public boolean active () { return enabled() && hasStarted() && !hasEnded(); } | |||
public boolean inactive () { return !active(); } | |||
@ECSearchable @ECField(index=100) | |||
@ECSearchable @ECField(index=110) | |||
@ECIndex @Column(nullable=false) | |||
@Getter @Setter private Boolean referral = false; | |||
public boolean referral () { return referral != null && referral; } | |||
@ECSearchable @ECField(index=110) | |||
@ECSearchable @ECField(index=120) | |||
@ECIndex @Column(nullable=false, updatable=false, length=10) | |||
@Getter @Setter private String currency; | |||
@@ -122,11 +127,11 @@ public class Promotion extends IdentifiableBase | |||
return currency != null && currency.equalsIgnoreCase(this.currency); | |||
} | |||
@ECSearchable @ECField(index=120) | |||
@ECSearchable @ECField(index=130) | |||
@ECIndex @Column(nullable=false, updatable=false) | |||
@Getter @Setter private Integer minValue = 100; | |||
@ECSearchable @ECField(index=130) | |||
@ECSearchable @ECField(index=140) | |||
@ECIndex @Column(nullable=false, updatable=false) | |||
@Getter @Setter private Integer maxValue; | |||
@@ -19,6 +19,7 @@ import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import lombok.ToString; | |||
import lombok.experimental.Accessors; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.collection.ArrayUtil; | |||
import org.cobbzilla.wizard.model.Identifiable; | |||
import org.cobbzilla.wizard.model.IdentifiableBase; | |||
@@ -51,7 +52,8 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||
@ECTypeChildren(uriPrefix=EP_NETWORKS+"/{BubbleNetwork.name}", value={ | |||
@ECTypeChild(type=BubbleNode.class, backref="network") | |||
}) | |||
@Entity @NoArgsConstructor @Accessors(chain=true) @ToString(of={"name", "domainName", "installType"}) | |||
@Entity @NoArgsConstructor @Accessors(chain=true) | |||
@Slf4j @ToString(of={"name", "domainName", "installType"}) | |||
@ECIndexes({ | |||
@ECIndex(unique=true, of={"account", "name"}), | |||
@ECIndex(unique=true, of={"name", "domainName"}) | |||
@@ -202,12 +204,14 @@ public class BubbleNetwork extends IdentifiableBase implements HasNetwork, HasBu | |||
if (network != null && !network.getUuid().equals(request.getNetwork())) { | |||
continue; | |||
} else { | |||
final Account acct = accountDAO.findByName(name); | |||
final Account acct = accountDAO.findByName(tryName); | |||
if (acct != null && !acct.getUuid().equals(request.getAccount())) { | |||
continue; | |||
} | |||
} | |||
return tryName.equals(name) ? errors : errors.setSuggestedName(tryName); | |||
if (tryName.equals(name)) return errors; | |||
log.info("validateHostname: setting suggested name='"+tryName+"'"); | |||
return errors.setSuggestedName(tryName); | |||
} | |||
errors.addViolation("err.name.alreadyInUse"); | |||
} | |||
@@ -66,11 +66,15 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||
return getDao().findByAccountAndNotDeleted(account.getUuid()); | |||
} | |||
@Override protected AccountPlan find(ContainerRequest ctx, String id) { | |||
return getDao().findByAccountAndIdAndNotDeleted(getAccountUuid(ctx), id); | |||
} | |||
@Override protected AccountPlan findAlternate(ContainerRequest ctx, String id) { | |||
// id might be a network uuid | |||
final String accountUuid = getAccountUuid(ctx); | |||
final BubbleNetwork network = networkDAO.findByAccountAndId(accountUuid, id); | |||
return network == null ? null : getDao().findByAccountAndNetwork(accountUuid, network.getUuid()); | |||
return network == null ? null : getDao().findByAccountAndNetworkAndNotDeleted(accountUuid, network.getUuid()); | |||
} | |||
@Override protected boolean canCreate(Request req, ContainerRequest ctx, Account caller, AccountPlan request) { | |||
@@ -149,6 +153,7 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||
} else { | |||
validateName(request, errors); | |||
} | |||
log.info("setReferences: after calling validateName, request.name="+request.getName()); | |||
final BubblePlan plan = planDAO.findByAccountOrParentAndId(caller, request.getPlan()); | |||
if (plan == null) { | |||
@@ -290,4 +290,27 @@ public class PromotionService { | |||
return listPromosForAccount(account.getUuid()); | |||
} | |||
public void applyLaunchFailurePromo(String accountUuid, String currency) { | |||
final List<AccountPaymentMethod> promos = accountPaymentMethodDAO.findByAccountAndPromoAndNotDeleted(accountUuid); | |||
// only include promos that are not already associated with the account | |||
final List<Promotion> launchFailPromos = promotionDAO.findEnabledAndActiveAndLaunchFailureWithNoCode(currency).stream() | |||
.filter(p -> promos.stream().noneMatch(ap -> ap.getPromotion().equals(p.getUuid()))) | |||
.collect(Collectors.toList()); | |||
if (!empty(launchFailPromos)) { | |||
final Promotion promo = launchFailPromos.get(0); | |||
log.info("applyLaunchFailurePromo: adding launchFailPromo: "+promo.getName()); | |||
accountPaymentMethodDAO.create(new AccountPaymentMethod() | |||
.setAccount(accountUuid) | |||
.setCloud(promo.getCloud()) | |||
.setPaymentMethodType(PaymentMethodType.promotional_credit) | |||
.setPaymentInfo(promo.getName()) | |||
.setMaskedPaymentInfo(promo.getName()) | |||
.setPromotion(promo.getUuid())); | |||
} | |||
} | |||
} |
@@ -40,6 +40,9 @@ public class NodeProgressMeter extends PipedOutputStream implements Runnable { | |||
private int tickPos = 0; | |||
private AtomicBoolean error = new AtomicBoolean(false); | |||
private AtomicBoolean closed = new AtomicBoolean(false); | |||
private AtomicBoolean success = new AtomicBoolean(false); | |||
public boolean success () { return success.get(); } | |||
private final Thread thread; | |||
private RedisService redis; | |||
@@ -159,6 +162,7 @@ public class NodeProgressMeter extends PipedOutputStream implements Runnable { | |||
public void completed() { | |||
closed.set(true); | |||
success.set(true); | |||
background(this::close); | |||
_setCurrentTick(new NodeProgressMeterTick() | |||
.setNetwork(nn.getNetwork()) | |||
@@ -29,6 +29,7 @@ import bubble.model.cloud.notify.NotificationType; | |||
import bubble.notify.NewNodeNotification; | |||
import bubble.server.BubbleConfiguration; | |||
import bubble.service.backup.RestoreService; | |||
import bubble.service.bill.PromotionService; | |||
import bubble.service.notify.NotificationService; | |||
import com.github.jknack.handlebars.Handlebars; | |||
import lombok.Cleanup; | |||
@@ -116,6 +117,7 @@ public class StandardNetworkService implements NetworkService { | |||
@Autowired private AccountPolicyDAO policyDAO; | |||
@Autowired private AccountMessageDAO accountMessageDAO; | |||
@Autowired private BubblePlanDAO planDAO; | |||
@Autowired private PromotionService promoService; | |||
@Autowired private NotificationService notificationService; | |||
@Autowired private NodeService nodeService; | |||
@@ -392,7 +394,18 @@ public class StandardNetworkService implements NetworkService { | |||
log.warn("newNode: compute.cleanupStart error: "+e, e); | |||
} | |||
} | |||
if (progressMeter != null) closeQuietly(progressMeter); | |||
if (progressMeter != null) { | |||
if (!progressMeter.success() && configuration.paymentsEnabled()) { | |||
final AccountPlan accountPlan = accountPlanDAO.findByNetwork(nn.getNetwork()); | |||
if (accountPlan != null) { | |||
final BubblePlan plan = planDAO.findByUuid(accountPlan.getPlan()); | |||
if (plan != null) { | |||
promoService.applyLaunchFailurePromo(nn.getAccount(), plan.getCurrency()); | |||
} | |||
} | |||
} | |||
closeQuietly(progressMeter); | |||
} | |||
unlockNetwork(nn.getNetwork(), lock); | |||
} | |||
return node; | |||
@@ -1 +1 @@ | |||
Subproject commit d673719b67c685457921fd3e9b8ea693b147c4a0 | |||
Subproject commit f5c2a0bdce5e4a9e8d610b1512c7ba4aa8e75828 |