use of org.candlepin.model.Product in project candlepin by candlepin.
the class CandlepinPoolManager method refreshPoolsWithRegeneration.
/*
* We need to update/regen entitlements in the same transaction we update pools
* so we don't miss anything
*/
@Transactional
@SuppressWarnings("checkstyle:methodlength")
@Traceable
void refreshPoolsWithRegeneration(SubscriptionServiceAdapter subAdapter, @TraceableParam("owner") Owner owner, boolean lazy) {
Date now = new Date();
owner = this.resolveOwner(owner);
log.info("Refreshing pools for owner: {}", owner);
Map<String, Subscription> subscriptionMap = new HashMap<>();
Map<String, ProductData> productMap = new HashMap<>();
Map<String, ContentData> contentMap = new HashMap<>();
// Resolve all our subscriptions, products and content to ensure we don't have bad or
// duplicate inbound data
log.debug("Fetching subscriptions from adapter...");
List<Subscription> subscriptions = subAdapter.getSubscriptions(owner);
log.debug("Done. Processing subscriptions...");
for (Subscription subscription : subscriptions) {
if (subscription == null) {
continue;
}
if (subscription.getId() == null) {
log.error("subscription does not contain a mappable ID: {}", subscription);
throw new IllegalStateException("subscription does not contain a mappable ID: " + subscription);
}
Subscription existingSub = subscriptionMap.get(subscription.getId());
if (existingSub != null && !existingSub.equals(subscription)) {
log.warn("Multiple versions of the same subscription received during refresh; " + "discarding duplicate: {} => {}, {}", subscription.getId(), existingSub, subscription);
continue;
}
subscriptionMap.put(subscription.getId(), subscription);
List<ProductData> products = new LinkedList<>();
products.add(subscription.getProduct());
products.add(subscription.getDerivedProduct());
products.addAll(subscription.getProvidedProducts());
products.addAll(subscription.getDerivedProvidedProducts());
for (ProductData product : products) {
if (product == null) {
// forward.
continue;
}
if (product.getId() == null) {
log.error("product does not contain a mappable Red Hat ID: {}", product);
throw new IllegalStateException("product does not contain a mappable Red Hat ID: " + product);
}
// Product is coming from an upstream source; lock it so only upstream can make
// further changes to it.
product.setLocked(true);
ProductData existingProduct = productMap.get(product.getId());
if (existingProduct != null && !existingProduct.equals(product)) {
log.warn("Multiple versions of the same product received during refresh; " + "discarding duplicate: {} => {}, {}", product.getId(), existingProduct, product);
} else {
productMap.put(product.getId(), product);
Collection<ProductContentData> pcdCollection = product.getProductContent();
if (pcdCollection != null) {
for (ProductContentData pcd : pcdCollection) {
if (pcd == null) {
log.error("product contains a null product-content mapping: {}", product);
throw new IllegalStateException("product contains a null product-content mapping: " + product);
}
ContentData content = pcd.getContent();
// population validation for us.
if (content == null || content.getId() == null) {
log.error("product contains a null or incomplete product-content mapping: {}", product);
throw new IllegalStateException("product contains a null or incomplete " + "product-content mapping: " + product);
}
// We need to lock the incoming content here, but doing so will affect
// the equality comparison for products. We'll correct them later.
ContentData existingContent = contentMap.get(content.getId());
if (existingContent != null && !existingContent.equals(content)) {
log.warn("Multiple versions of the same content received during refresh; " + "discarding duplicate: {} => {}, {}", content.getId(), existingContent, content);
} else {
contentMap.put(content.getId(), content);
}
}
}
}
}
}
// Persist content changes
log.debug("Importing {} content...", contentMap.size());
// TODO: Find a more efficient way of doing this, preferably within this method
for (ContentData cdata : contentMap.values()) {
cdata.setLocked(true);
}
Map<String, Content> importedContent = this.contentManager.importContent(owner, contentMap, productMap.keySet()).getImportedEntities();
log.debug("Importing {} product(s)...", productMap.size());
ImportResult<Product> importResult = this.productManager.importProducts(owner, productMap, importedContent);
Map<String, Product> importedProducts = importResult.getImportedEntities();
Map<String, Product> updatedProducts = importResult.getUpdatedEntities();
log.debug("Refreshing {} pool(s)...", subscriptionMap.size());
Iterator<Map.Entry<String, Subscription>> subsIterator = subscriptionMap.entrySet().iterator();
while (subsIterator.hasNext()) {
Map.Entry<String, Subscription> entry = subsIterator.next();
Subscription sub = entry.getValue();
if (now.after(sub.getEndDate())) {
log.info("Skipping expired subscription: {}", sub);
subsIterator.remove();
continue;
}
log.debug("Processing subscription: {}", sub);
Pool pool = this.convertToMasterPoolImpl(sub, owner, importedProducts);
this.refreshPoolsForMasterPool(pool, false, lazy, updatedProducts);
}
// delete pools whose subscription disappeared:
log.debug("Deleting pools for absent subscriptions...");
List<Pool> poolsToDelete = new ArrayList<>();
for (Pool pool : poolCurator.getPoolsFromBadSubs(owner, subscriptionMap.keySet())) {
if (this.isManaged(pool)) {
poolsToDelete.add(pool);
}
}
deletePools(poolsToDelete);
// TODO: break this call into smaller pieces. There may be lots of floating pools
log.debug("Updating floating pools...");
List<Pool> floatingPools = poolCurator.getOwnersFloatingPools(owner);
updateFloatingPools(floatingPools, lazy, updatedProducts);
log.info("Refresh pools for owner: {} completed in: {}ms", owner.getKey(), System.currentTimeMillis() - now.getTime());
}
use of org.candlepin.model.Product in project candlepin by candlepin.
the class CandlepinPoolManager method refreshPoolsForMasterPool.
@Transactional
void refreshPoolsForMasterPool(Pool pool, boolean updateStackDerived, boolean lazy, Map<String, Product> changedProducts) {
// These don't all necessarily belong to this owner
List<Pool> subscriptionPools;
if (pool.getSubscriptionId() != null) {
subscriptionPools = this.poolCurator.getPoolsBySubscriptionId(pool.getSubscriptionId()).list();
} else {
// If we don't have a subscription ID, this *is* the master pool, but we need to use
// the original, hopefully unmodified pool
subscriptionPools = pool.getId() != null ? Collections.<Pool>singletonList(this.poolCurator.find(pool.getId())) : Collections.<Pool>singletonList(pool);
}
log.debug("Found {} pools for subscription {}", subscriptionPools.size(), pool.getSubscriptionId());
if (log.isDebugEnabled()) {
for (Pool p : subscriptionPools) {
log.debug(" owner={} - {}", p.getOwner().getKey(), p);
}
}
// TODO: Should this be performed by poolRules? Seems like that should be a thing.
for (Pool subPool : subscriptionPools) {
Product product = subPool.getProduct();
if (product != null) {
Product update = changedProducts.get(product.getId());
if (update != null) {
subPool.setProduct(update);
}
}
product = subPool.getDerivedProduct();
if (product != null) {
Product update = changedProducts.get(product.getId());
if (update != null) {
subPool.setDerivedProduct(update);
}
}
}
// Cleans up pools on other owners who have migrated subs away
removeAndDeletePoolsOnOtherOwners(subscriptionPools, pool);
// capture the original quantity to check for updates later
Long originalQuantity = pool.getQuantity();
// BZ 1012386: This will regenerate master/derived for bonus scenarios if only one of the
// pair still exists.
createAndEnrichPools(pool, subscriptionPools);
// don't update floating here, we'll do that later so we don't update anything twice
Set<String> updatedMasterPools = updatePoolsForMasterPool(subscriptionPools, pool, originalQuantity, updateStackDerived, changedProducts);
regenerateCertificatesByEntIds(updatedMasterPools, lazy);
}
use of org.candlepin.model.Product in project candlepin by candlepin.
the class CandlepinPoolManager method convertToMasterPool.
/*
* if you are using this method, you might want to override the quantity
* with PoolRules.calculateQuantity
*/
@Override
public Pool convertToMasterPool(Subscription sub) {
if (sub == null) {
throw new IllegalArgumentException("subscription is null");
}
// Resolve the subscription's owner...
if (sub.getOwner() == null || (sub.getOwner().getId() == null && sub.getOwner().getKey() == null)) {
throw new IllegalStateException("Subscription references an invalid owner: " + sub.getOwner());
}
Owner owner = sub.getOwner().getId() != null ? this.ownerCurator.find(sub.getOwner().getId()) : this.ownerCurator.lookupByKey(sub.getOwner().getKey());
if (owner == null) {
throw new IllegalStateException("Subscription references an owner which cannot be resolved: " + sub.getOwner());
}
// Gather the product IDs referenced by this subscription...
Set<ProductData> productData = new HashSet<>();
Set<String> productIds = new HashSet<>();
Map<String, Product> productMap = new HashMap<>();
productData.add(sub.getProduct());
productData.add(sub.getDerivedProduct());
if (sub.getProvidedProducts() != null) {
productData.addAll(sub.getProvidedProducts());
}
if (sub.getDerivedProvidedProducts() != null) {
productData.addAll(sub.getDerivedProvidedProducts());
}
for (ProductData pdata : productData) {
if (pdata != null) {
if (pdata.getId() == null) {
throw new IllegalStateException("Subscription references an incomplete product: " + pdata);
}
productIds.add(pdata.getId());
}
}
// Build the product map from the product IDs we pulled off the subscription...
for (Product product : this.ownerProductCurator.getProductsByIds(owner, productIds)) {
productMap.put(product.getId(), product);
}
return this.convertToMasterPoolImpl(sub, owner, productMap);
}
use of org.candlepin.model.Product in project candlepin by candlepin.
the class ContentManager method updateContent.
/**
* Updates the specified content instance, creating a new version of the content as necessary.
* The content instance returned by this method is not guaranteed to be the same instance passed
* in. As such, once this method has been called, callers should only use the instance output by
* this method.
*
* @param owner
* The owner for which to update the content
*
* @param regenerateEntitlementCerts
* Whether or not changes made to the content should trigger the regeneration of entitlement
* certificates for affected consumers
*
* @throws IllegalStateException
* if the given content update references a content that does not exist for the specified owner
*
* @throws IllegalArgumentException
* if either the provided content entity or owner are null
*
* @return
* the updated content entity, or a new content entity
*/
@Transactional
public Content updateContent(ContentDTO update, Owner owner, boolean regenerateEntitlementCerts) {
if (update == null) {
throw new IllegalArgumentException("update is null");
}
if (update.getId() == null) {
throw new IllegalArgumentException("update is incomplete");
}
if (owner == null) {
throw new IllegalArgumentException("owner is null");
}
// Resolve the entity to ensure we're working with the merged entity, and to ensure it's
// already been created.
// TODO: FIXME:
// There's a bug here where if changes are applied to an entity's collections, and then
// this method is called, the check below will cause the changes to be persisted.
// This needs to be re-written to use DTOs as the primary source of entity creation, rather
// than a bolted-on utility method.
// If we never edit the entity directly, however, this is safe.
Content entity = this.ownerContentCurator.getContentById(owner, update.getId());
if (entity == null) {
// If we're doing an exclusive update, this should be an error condition
throw new IllegalStateException("Content has not yet been created");
}
// TODO: Remove this shim and stop using DTOs in this class
if (!this.isChangedBy(entity, update)) {
return entity;
}
log.debug("Applying content update for org: {}, {}", entity, owner);
Content updated = this.applyContentChanges((Content) entity.clone(), update);
List<Content> alternateVersions = this.ownerContentCurator.getContentByVersions(owner, Collections.<String, Integer>singletonMap(updated.getId(), updated.getEntityVersion())).list();
log.debug("Checking {} alternate content versions", alternateVersions.size());
for (Content alt : alternateVersions) {
if (alt.equals(updated)) {
log.debug("Converging product with existing: {} => {}", updated, alt);
// Make sure every product using the old version/entity are updated to use the new one
List<Product> affectedProducts = this.productCurator.getProductsByContent(owner, Arrays.asList(updated.getId())).list();
this.ownerContentCurator.updateOwnerContentReferences(owner, Collections.<String, String>singletonMap(entity.getUuid(), alt.getUuid()));
log.debug("Updating {} affected products", affectedProducts.size());
ContentDTO cdto = this.modelTranslator.translate(alt, ContentDTO.class);
// TODO: Should we bulk this up like we do in importContent? Probably.
for (Product product : affectedProducts) {
log.debug("Updating affected product: {}", product);
ProductDTO pdto = this.modelTranslator.translate(product, ProductDTO.class);
ProductContentDTO pcdto = pdto.getProductContent(cdto.getId());
if (pcdto != null) {
pdto.addContent(cdto, pcdto.isEnabled());
// Impl note: This should also take care of our entitlement cert regeneration
this.productManager.updateProduct(pdto, owner, regenerateEntitlementCerts);
}
}
return alt;
}
}
// Temporarily (?) disabled. If we ever move to clustered caching (rather than per-instance
// caching, this branch should be re-enabled.
/*
// No alternate versions with which to converge. Check if we can do an in-place update instead
if (this.ownerContentCurator.getOwnerCount(updated) < 2) {
log.debug("Applying in-place update to content: {}", updated);
updated = this.contentCurator.merge(this.applyContentChanges(entity, update, owner));
if (regenerateEntitlementCerts) {
// Every owner with a pool using any of the affected products needs an update.
List<Product> affectedProducts = this.productCurator
.getProductsByContent(Arrays.asList(updated.getUuid()))
.list();
this.entitlementCertGenerator.regenerateCertificatesOf(
Arrays.asList(owner), affectedProducts, true
);
}
return updated;
}
*/
log.debug("Forking content and applying update: {}", updated);
// Get products that currently use this content...
List<Product> affectedProducts = this.productCurator.getProductsByContent(owner, Arrays.asList(updated.getId())).list();
// Clear the UUID so Hibernate doesn't think our copy is a detached entity
updated.setUuid(null);
updated = this.contentCurator.create(updated);
this.ownerContentCurator.updateOwnerContentReferences(owner, Collections.<String, String>singletonMap(entity.getUuid(), updated.getUuid()));
// Impl note:
// This block is a consequence of products and contents not being strongly related.
log.debug("Updating {} affected products", affectedProducts.size());
ContentDTO cdto = this.modelTranslator.translate(updated, ContentDTO.class);
// TODO: Should we bulk this up like we do in importContent? Probably.
for (Product product : affectedProducts) {
log.debug("Updating affected product: {}", product);
ProductDTO pdto = this.modelTranslator.translate(product, ProductDTO.class);
ProductContentDTO pcdto = pdto.getProductContent(cdto.getId());
if (pcdto != null) {
pdto.addContent(cdto, pcdto.isEnabled());
// Impl note: This should also take care of our entitlement cert regeneration
this.productManager.updateProduct(pdto, owner, regenerateEntitlementCerts);
}
}
return updated;
}
use of org.candlepin.model.Product in project candlepin by candlepin.
the class ProductManager method removeProduct.
/**
* Removes the specified product from the given owner. If the product is in use by multiple
* owners, the product will not actually be deleted, but, instead, will simply by removed from
* the given owner's visibility.
*
* @param owner
* The owner for which to remove the product
*
* @param entity
* The product entity to remove
*
* @throws IllegalStateException
* if this method is called with an entity does not exist in the backing database for the given
* owner, or if the product is currently in use by one or more subscriptions/pools
*
* @throws IllegalArgumentException
* if entity or owner is null
*/
public void removeProduct(Owner owner, Product entity) {
if (owner == null) {
throw new IllegalArgumentException("owner is null");
}
if (entity == null) {
throw new IllegalArgumentException("entity is null");
}
// This has to fetch a new instance, or we'll be unable to compare the objects
Product existing = this.ownerProductCurator.getProductById(owner, entity.getId());
if (existing == null) {
// If we're doing an exclusive update, this should be an error condition
throw new IllegalStateException("Product has not yet been created");
}
this.removeProductsByUuids(owner, Arrays.asList(existing.getUuid()));
}
Aggregations