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