@@ -2,30 +2,50 @@ package bubble.dao.cloud; | |||
import bubble.cloud.storage.local.LocalStorageDriver; | |||
import bubble.dao.account.AccountOwnedTemplateDAO; | |||
import bubble.model.cloud.BubbleNetwork; | |||
import bubble.model.cloud.CloudService; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.server.BubbleConfiguration; | |||
import org.cobbzilla.wizard.validation.ValidationResult; | |||
import org.hibernate.criterion.Order; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Repository; | |||
import java.util.List; | |||
import static bubble.ApiConstants.ROOT_NETWORK_UUID; | |||
import static bubble.cloud.storage.local.LocalStorageDriver.LOCAL_STORAGE; | |||
import static bubble.model.cloud.CloudService.testDriver; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
@Repository | |||
public class CloudServiceDAO extends AccountOwnedTemplateDAO<CloudService> { | |||
@Autowired private BubbleConfiguration configuration; | |||
@Override public Order getDefaultSortOrder() { return Order.desc("priority"); } | |||
@Override public Object preCreate(CloudService cloud) { | |||
if (cloud.getType() == CloudServiceType.storage | |||
&& cloud.getName().equals(LOCAL_STORAGE) | |||
&& !cloud.getDriver().getClass().equals(LocalStorageDriver.class)) { | |||
throw invalidEx("err.cloud.localStorageIsReservedName"); | |||
if (cloud.getType() == CloudServiceType.storage) { | |||
if (cloud.getName().equals(LOCAL_STORAGE) && !cloud.getDriver().getClass().equals(LocalStorageDriver.class)) { | |||
throw invalidEx("err.cloud.localStorageIsReservedName"); | |||
} else if (cloud.isNotLocalStorage()) { | |||
final BubbleNetwork thisNetwork = configuration.getThisNetwork(); | |||
final String networkUuid = thisNetwork == null ? ROOT_NETWORK_UUID : thisNetwork.getUuid(); | |||
if (cloud.hasCredentials() && cloud.getCredentials().needsNewNetworkKey(networkUuid)) { | |||
cloud.setCredentials(cloud.getCredentials().initNetworkKey(networkUuid)); | |||
} | |||
} | |||
} | |||
return super.preCreate(cloud); | |||
} | |||
@Override public CloudService postCreate(CloudService cloud, Object context) { | |||
final ValidationResult errors = testDriver(cloud, configuration); | |||
if (errors.isInvalid()) throw invalidEx(errors); | |||
return super.postCreate(cloud, context); | |||
} | |||
@Override public CloudService postUpdate(CloudService cloud, Object context) { | |||
CloudService.clearDriverCache(cloud.getUuid()); | |||
return super.postUpdate(cloud, context); | |||
@@ -33,6 +33,7 @@ import org.cobbzilla.wizard.model.Identifiable; | |||
import org.cobbzilla.wizard.model.entityconfig.IdentifiableBaseParentEntity; | |||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | |||
import org.cobbzilla.wizard.validation.HasValue; | |||
import org.cobbzilla.wizard.validation.SimpleViolationException; | |||
import org.cobbzilla.wizard.validation.ValidationResult; | |||
import org.hibernate.annotations.Type; | |||
@@ -68,6 +69,7 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun | |||
new ScrubbableField(CloudService.class, "credentials", CloudCredentials.class), | |||
new ScrubbableField(CloudService.class, "credentialsJson", String.class) | |||
}; | |||
@Override public ScrubbableField[] fieldsToScrub() { return SCRUB_FIELDS; } | |||
public static final String[] UPDATE_FIELDS = {"description", "template", "enabled", "driverConfig", "priority"}; | |||
@@ -227,20 +229,26 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun | |||
public <T extends CloudServiceDriver> T wireAndSetup (BubbleConfiguration configuration) { | |||
// note: CloudServiceDAO calls clearDriverCache when driver config is updated, | |||
// then the updated class/config/credentials will be used. | |||
return (T) driverCache.computeIfAbsent(getUuid(), k -> { | |||
final T driver; | |||
if (delegated()) { | |||
if (type.hasDelegateDriverClass()) { | |||
driver = (T) configuration.autowire(instantiate(type.getDelegateDriverClass(), this)); | |||
} else { | |||
return die("wireAndSetup: cloud service type " + type + " does not support delegation: class not found: "+type.getDelegateDriverClassName()); | |||
} | |||
if (!hasUuid()) { | |||
// this is a test before creation, just try to wire it up, but do not cache the result | |||
return _wireAndSetup(configuration); | |||
} | |||
return (T) driverCache.computeIfAbsent(getUuid(), k -> _wireAndSetup(configuration)); | |||
} | |||
private <T extends CloudServiceDriver> T _wireAndSetup (BubbleConfiguration configuration) { | |||
final T driver; | |||
if (delegated()) { | |||
if (type.hasDelegateDriverClass()) { | |||
driver = (T) configuration.autowire(instantiate(type.getDelegateDriverClass(), this)); | |||
} else { | |||
driver = (T) configuration.autowire(getDriver()); | |||
driver.postSetup(); | |||
return die("wireAndSetup: cloud service type " + type + " does not support delegation: class not found: "+type.getDelegateDriverClassName()); | |||
} | |||
return driver; | |||
}); | |||
} else { | |||
driver = (T) configuration.autowire(getDriver()); | |||
driver.postSetup(); | |||
} | |||
return driver; | |||
} | |||
public CloudService configure(CloudServiceConfig config, ValidationResult errors) { | |||
@@ -290,4 +298,40 @@ public class CloudService extends IdentifiableBaseParentEntity implements Accoun | |||
} | |||
return config; | |||
} | |||
@Transient @JsonIgnore @Getter @Setter private Object testArg = null; | |||
public static ValidationResult testDriver(CloudService cloud, BubbleConfiguration configuration) { | |||
return testDriver(cloud, configuration, new ValidationResult()); | |||
} | |||
public static ValidationResult testDriver(CloudService cloud, BubbleConfiguration configuration, ValidationResult errors) { | |||
final String prefix = cloud.getName()+": "; | |||
final Object arg = cloud.getTestArg(); | |||
final String argString = arg != null ? " with arg=" + arg : ""; | |||
final String invalidValue = arg == null ? null : arg.toString(); | |||
final String driverClass = cloud.getDriverClass(); | |||
final String errTestFailed = "err."+cloud.getType()+".testFailed"; | |||
final String errException = "err."+cloud.getType()+".unknownError"; | |||
final CloudServiceDriver driver; | |||
try { | |||
driver = cloud.getConfiguredDriver(configuration); | |||
} catch (SimpleViolationException e) { | |||
return errors.addViolation(e.getBean()); | |||
} catch (Exception e) { | |||
return errors.addViolation(errTestFailed, prefix+"driver initialization failed: "+driverClass+": "+shortError(e)); | |||
} | |||
try { | |||
if (!driver.test(arg)) { | |||
return errors.addViolation(errTestFailed, prefix+"test failed for driver: "+driverClass+argString, invalidValue); | |||
} | |||
} catch (SimpleViolationException e) { | |||
return errors.addViolation(e.getBean()); | |||
} catch (Exception e) { | |||
return errors.addViolation(errException, prefix+"test failed for driver: "+driverClass+argString+": "+shortError(e), invalidValue); | |||
} | |||
return errors; | |||
} | |||
} |
@@ -26,15 +26,6 @@ public class CloudServicesResource extends AccountOwnedResource<CloudService, Cl | |||
public CloudServicesResource(Account account) { super(account); } | |||
@Override protected Object daoCreate(CloudService cloud) { | |||
try { | |||
cloud.wireAndSetup(configuration); | |||
} catch (Exception e) { | |||
throw invalidEx("err.driverConfig.initFailure"); | |||
} | |||
return super.daoCreate(cloud); | |||
} | |||
@Override protected List<CloudService> list(Request req, ContainerRequest ctx) { | |||
final Map<String, String> queryParams = queryParams(req.getQueryString()); | |||
final String type = queryParams.get("type"); | |||
@@ -1,6 +1,5 @@ | |||
package bubble.service.boot; | |||
import bubble.cloud.CloudServiceDriver; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.compute.ComputeNodeSizeType; | |||
import bubble.cloud.compute.local.LocalComputeDriver; | |||
@@ -21,7 +20,6 @@ import org.cobbzilla.wizard.api.CrudOperation; | |||
import org.cobbzilla.wizard.client.ApiClientBase; | |||
import org.cobbzilla.wizard.model.Identifiable; | |||
import org.cobbzilla.wizard.model.ModelSetupService; | |||
import org.cobbzilla.wizard.validation.SimpleViolationException; | |||
import org.cobbzilla.wizard.validation.ValidationResult; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
@@ -128,24 +126,19 @@ public class ActivationService { | |||
.setTemplate(true)); | |||
} | |||
// create all clouds | |||
// create clouds, test cloud drivers | |||
for (CloudService cloud : toCreate) { | |||
cloudDAO.create(cloud | |||
.setTemplate(true) | |||
.setEnabled(true) | |||
.setAccount(account.getUuid())); | |||
final Object testArg; | |||
if (cloud == publicDns) { | |||
checkDriver(cloud, errors, request.getDomain().getName(), "err.dns.testFailed", "err.dns.unknownError"); | |||
} else if (cloud == networkStorage) { | |||
if (networkStorage.getCredentials().needsNewNetworkKey(ROOT_NETWORK_UUID)) { | |||
networkStorage.setCredentials(networkStorage.getCredentials().initNetworkKey(ROOT_NETWORK_UUID)); | |||
} | |||
checkDriver(cloud, errors, null, "err.storage.testFailed", "err.storage.unknownError"); | |||
testArg = request.getDomain().getName(); | |||
} else { | |||
checkDriver(cloud, errors, null, "err."+cloud.getType()+".testFailed", "err."+cloud.getType()+".unknownError"); | |||
testArg = null; | |||
} | |||
cloudDAO.create(cloud | |||
.setTemplate(true) | |||
.setEnabled(true) | |||
.setAccount(account.getUuid()) | |||
.setTestArg(testArg)); | |||
} | |||
if (errors.isInvalid()) throw invalidEx(errors); | |||
@@ -235,34 +228,6 @@ public class ActivationService { | |||
return node; | |||
} | |||
public ValidationResult checkDriver(CloudService cloud, ValidationResult errors, Object arg, String errTestFailed, String errException) { | |||
final String prefix = cloud.getName()+": "; | |||
final String argString = arg != null ? " with arg=" + arg : ""; | |||
final String invalidValue = arg == null ? null : arg.toString(); | |||
final String driverClass = cloud.getDriverClass(); | |||
final CloudServiceDriver driver; | |||
try { | |||
driver = cloud.getConfiguredDriver(configuration); | |||
} catch (SimpleViolationException e) { | |||
return errors.addViolation(e.getBean()); | |||
} catch (Exception e) { | |||
return errors.addViolation(errTestFailed, prefix+"driver initialization failed: "+driverClass); | |||
} | |||
try { | |||
if (!driver.test(arg)) { | |||
return errors.addViolation(errTestFailed, prefix+"test failed for driver: "+driverClass+argString, invalidValue); | |||
} | |||
} catch (SimpleViolationException e) { | |||
return errors.addViolation(e.getBean()); | |||
} catch (Exception e) { | |||
return errors.addViolation(errException, prefix+"test failed for driver: "+driverClass+argString+": "+shortError(e), invalidValue); | |||
} | |||
return errors; | |||
} | |||
public String loadDefaultRoles() { | |||
if (configuration.testMode()) { | |||
final File roleFile = new File("target/classes/"+DEFAULT_ROLES); | |||
@@ -140,7 +140,6 @@ button_label_submit_verify_code=Verify | |||
err.token.invalid=Code is incorrect | |||
# Low-level errors and activation errors | |||
err.driverConfig.initFailure=Cloud driver failed to initialize properlyu | |||
err.cloud.noSuchField=A cloud driver config field name is invalid | |||
err.cloud.invalidFieldType=Cloud driver config field type invalid | |||
err.cloud.notFound=No cloud exists with this name | |||
@@ -26,6 +26,7 @@ | |||
{ | |||
"name": "FreePlay", | |||
"type": "payment", | |||
"priority": 300, | |||
"driverClass": "bubble.cloud.payment.free.FreePaymentDriver", | |||
"driverConfig": {}, | |||
"credentials": {}, | |||