use of org.ligoj.app.plugin.prov.model.ProvQuoteDatabase in project plugin-prov by ligoj.
the class ProvQuoteDatabaseResourceTest method lookupLicenseIncluded.
/**
* Basic case, almost no requirements but license.
*/
@Test
void lookupLicenseIncluded() {
final var lookup = qbResource.lookup(subscription, builder().usage("Full Time 12 month").license("INCLUDED").engine("MYSQL").build());
// Check the instance result
final var pi = lookup.getPrice();
Assertions.assertEquals("database2", pi.getType().getName());
Assertions.assertEquals("MYSQL3", pi.getCode());
Assertions.assertEquals("MYSQL", pi.getEngine());
Assertions.assertNull(pi.getStorageEngine());
Assertions.assertNull(pi.getEdition());
Assertions.assertNull(pi.getLicense());
// Coverage only
Assertions.assertTrue(pi.toString().contains("engine=MYSQL, edition=null"));
Assertions.assertTrue(lookup.toString().contains("engine=MYSQL, edition=null"));
new ProvQuoteDatabase().setStorages(null);
Assertions.assertNotNull(qbResource.getItRepository());
}
use of org.ligoj.app.plugin.prov.model.ProvQuoteDatabase in project plugin-prov by ligoj.
the class ProvQuoteUploadResource method upload.
/**
* Upload a file of quote.
*
* @param subscription The subscription identifier, will be used to filter the locations from the associated
* provider.
* @param uploadedFile Instance entries files to import. Currently support only CSV format.
* @param headers the CSV header names. When <code>null</code> or empty, the default headers are used.
* @param headersIncluded When <code>true</code>, the first line is the headers and the given <code>headers</code>
* parameter is ignored. Otherwise the <code>headers</code> parameter is used.
* @param defaultUsage The optional usage name. When not <code>null</code>, each quote instance without defined
* usage will be associated to this usage.
* @param mode The merge option indicates how the entries are inserted.
* @param ramMultiplier The multiplier for imported RAM values. Default is 1.
* @param encoding CSV encoding. Default is UTF-8.
* @param errorContinue When <code>true</code> errors do not block the upload.
* @param createUsage When <code>true</code>, missing usage are automatically created.
* @param separator CSV separator. Default is ";".
* @throws IOException When the CSV stream cannot be written.
*/
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("{subscription:\\d+}/upload")
public void upload(@PathParam("subscription") final int subscription, @Multipart(value = CSV_FILE) final InputStream uploadedFile, @Multipart(value = "headers", required = false) final String[] headers, @Multipart(value = "headers-included", required = false) final boolean headersIncluded, @Multipart(value = "usage", required = false) final String defaultUsage, @Multipart(value = "mergeUpload", required = false) final MergeMode mode, @Multipart(value = "memoryUnit", required = false) final Integer ramMultiplier, @Multipart(value = "errorContinue", required = false) final boolean errorContinue, @Multipart(value = "encoding", required = false) final String encoding, @Multipart(value = "createMissingUsage", required = false) final boolean createUsage, @Multipart(value = "separator", required = false) final String separator) throws IOException {
log.info("Upload provisioning requested...");
subscriptionResource.checkVisible(subscription);
final var quote = resource.getRepository().findBy("subscription.id", subscription);
final var safeEncoding = ObjectUtils.defaultIfNull(encoding, DEFAULT_ENCODING);
// Check headers validity
final String[] headersArray;
final InputStream fileNoHeader;
if (headersIncluded) {
// Header at first line
final var br = new BufferedReader(new StringReader(IOUtils.toString(uploadedFile, safeEncoding)));
headersArray = StringUtils.defaultString(br.readLine()).split(separator);
fileNoHeader = new ByteArrayInputStream(IOUtils.toByteArray(br, safeEncoding));
} else {
// Headers are provided separately
headersArray = ArrayUtils.isEmpty(headers) ? DEFAULT_HEADERS : headers;
fileNoHeader = uploadedFile;
}
final var headersArray2 = checkHeaders(headersArray);
final var headersString = StringUtils.chop(ArrayUtils.toString(headersArray2)).substring(1).replace(",", separator) + "\n";
final var reader = new InputStreamReader(new SequenceInputStream(new ByteArrayInputStream(headersString.getBytes(safeEncoding)), fileNoHeader), safeEncoding);
// Build entries
log.info("Upload provisioning : reading, using header {}", headersString);
final var list = csvForBean.toBean(VmUpload.class, reader);
log.info("Upload provisioning : importing {} entries", list.size());
final var cursor = new AtomicInteger(0);
final var previousQi = qiRepository.findAll(quote).stream().collect(Collectors.toConcurrentMap(ProvQuoteInstance::getName, Function.identity()));
final var previousQb = qbRepository.findAll(quote).stream().collect(Collectors.toConcurrentMap(ProvQuoteDatabase::getName, Function.identity()));
// Initialization for parallel process
Hibernate.initialize(quote.getUsages());
Hibernate.initialize(quote.getBudgets());
final var context = new UploadContext();
context.quote = quote;
context.previousQi = previousQi;
context.previousQb = previousQb;
list.stream().filter(Objects::nonNull).filter(i -> i.getName() != null).forEach(i -> {
try {
persist(subscription, defaultUsage, mode, ramMultiplier, list.size(), cursor, context, createUsage, i);
} catch (final ValidationJsonException e) {
handleUploadError(errorContinue, handleValidationError(i, e));
} catch (final ConstraintViolationException e) {
handleUploadError(errorContinue, handleValidationError(i, new ValidationJsonException(e)));
} catch (final RuntimeException e) {
log.error("Unmanaged error during import of " + i.getName(), e);
handleUploadError(errorContinue, e);
}
});
log.info("Upload provisioning : flushing");
}
use of org.ligoj.app.plugin.prov.model.ProvQuoteDatabase in project plugin-prov by ligoj.
the class ProvBudgetResource method pack.
private double pack(final ProvBudget budget, final Map<Double, AbstractQuoteVm<?>> packToQr, final Map<AbstractQuoteVm<?>, FloatingPrice<?>> prices, final List<ProvQuoteInstance> validatedQi, final List<ProvQuoteDatabase> validatedQb, final List<ProvQuoteContainer> validatedQc, final List<ProvQuoteFunction> validatedQf, final Map<ResourceType, Map<Integer, FloatingCost>> costs) {
if (packToQr.isEmpty()) {
return 0d;
}
// At least one initial cost is implied, use bin packing strategy
final var packStart = System.currentTimeMillis();
final var packer = new LinearBinPacker();
final var bins = packer.packAll(packToQr.entrySet().stream().sorted(priceOrder(prices)).map(Entry::getKey).collect(Collectors.toList()), new ArrayList<>(List.of(new LinearBin(budget.getRemainingBudget()))), new ArrayList<>(List.of(Double.MAX_VALUE)));
final var bin = bins.get(0);
bin.getPieces().stream().map(packToQr::get).forEach(i -> {
if (i.getResourceType() == ResourceType.INSTANCE) {
validatedQi.add((ProvQuoteInstance) i);
} else if (i.getResourceType() == ResourceType.DATABASE) {
validatedQb.add((ProvQuoteDatabase) i);
} else if (i.getResourceType() == ResourceType.CONTAINER) {
validatedQc.add((ProvQuoteContainer) i);
} else {
validatedQf.add((ProvQuoteFunction) i);
}
});
logLean(b -> {
log.info("Packing result: {}", b.get(0).getPieces().stream().map(packToQr::get).map(i -> i.getName() + CODE + i.getPrice().getCode() + ")").collect(Collectors.toList()));
log.info("Packing result: {}", b);
}, bins);
logPack(packStart, packToQr, budget);
var init = bin.getTotal();
if (bins.size() > 1) {
// Extra bin needs to make a new pass
budget.setRemainingBudget(FloatingCost.round(budget.getRemainingBudget() - bin.getTotal()));
final List<ProvQuoteInstance> subQi = newSubPack(packToQr, bins, ResourceType.INSTANCE);
final List<ProvQuoteDatabase> subQb = newSubPack(packToQr, bins, ResourceType.DATABASE);
final List<ProvQuoteContainer> subQc = newSubPack(packToQr, bins, ResourceType.CONTAINER);
final List<ProvQuoteFunction> subQf = newSubPack(packToQr, bins, ResourceType.FUNCTION);
init += leanRecursive(budget, subQi, subQb, subQc, subQf, costs);
} else {
// Pack is completed
}
return init;
}
Aggregations