Package org.candlepin.policy.js.pool

Source Code of org.candlepin.policy.js.pool.PoolRules

/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.policy.js.pool;

import org.candlepin.common.config.Configuration;
import org.candlepin.config.ConfigProperties;
import org.candlepin.controller.PoolManager;
import org.candlepin.model.Branding;
import org.candlepin.model.Consumer;
import org.candlepin.model.DerivedProvidedProduct;
import org.candlepin.model.Entitlement;
import org.candlepin.model.EntitlementCurator;
import org.candlepin.model.Pool;
import org.candlepin.model.Product;
import org.candlepin.model.ProductAttribute;
import org.candlepin.model.ProductPoolAttribute;
import org.candlepin.model.ProvidedProduct;
import org.candlepin.model.SourceSubscription;
import org.candlepin.model.Subscription;
import org.candlepin.policy.js.ProductCache;

import com.google.inject.Inject;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Rules for creation and updating of pools during a refresh pools operation.
*/
public class PoolRules {

    private static Logger log = LoggerFactory.getLogger(PoolRules.class);

    private PoolManager poolManager;
    private ProductCache productCache;
    private Configuration config;
    private EntitlementCurator entCurator;


    @Inject
    public PoolRules(PoolManager poolManager, ProductCache productCache, Configuration config,
        EntitlementCurator entCurator) {
        this.poolManager = poolManager;
        this.productCache = productCache;
        this.config = config;
        this.entCurator = entCurator;
    }

    private long calculateQuantity(Subscription sub) {
        long quantity = sub.getQuantity() * sub.getProduct().getMultiplier();

        // In hosted, we increase the quantity on the subscription. However in standalone,
        // we assume this already has happened in hosted and the accurate quantity was
        // exported:
        if (sub.getProduct().hasAttribute("instance_multiplier") &&
            sub.getUpstreamPoolId() == null) {

            int instanceMultiplier = Integer.parseInt(
                sub.getProduct().getAttribute("instance_multiplier").getValue());
            log.debug("Increasing pool quantity for instance multiplier: " +
                instanceMultiplier);
            quantity = quantity * instanceMultiplier;
        }
        return quantity;
    }

    public List<Pool> createPools(Subscription sub) {
        return createPools(sub, new LinkedList<Pool>());
    }

    public List<Pool> createPools(Subscription sub, List<Pool> existingPools) {
        log.info("Creating pools for subscription: " + sub);
        PoolHelper helper = new PoolHelper(this.poolManager,
            this.productCache, null);

        List<Pool> pools = new LinkedList<Pool>();
        Map<String, String> attributes =
            helper.getFlattenedAttributes(sub.getProduct());
        long quantity = calculateQuantity(sub);
        Set<ProvidedProduct> providedProducts = new HashSet<ProvidedProduct>();
        Set<DerivedProvidedProduct> subProvidedProducts =
            new HashSet<DerivedProvidedProduct>();

        if (!hasMasterPool(existingPools)) {
            Pool newPool = new Pool(sub.getOwner(), sub.getProduct().getId(),
                    sub.getProduct().getName(), providedProducts, quantity, sub.getStartDate(),
                    sub.getEndDate(), sub.getContractNumber(), sub.getAccountNumber(),
                    sub.getOrderNumber());
            newPool.setDerivedProvidedProducts(subProvidedProducts);

            if (sub.getProvidedProducts() != null) {
                for (Product p : sub.getProvidedProducts()) {
                    ProvidedProduct providedProduct = new ProvidedProduct(p.getId(),
                        p.getName());
                    providedProduct.setPool(newPool);
                    providedProducts.add(providedProduct);
                }
            }

            if (sub.getDerivedProvidedProducts() != null) {
                for (Product p : sub.getDerivedProvidedProducts()) {
                    DerivedProvidedProduct providedProduct =
                        new DerivedProvidedProduct(p.getId(), p.getName());
                    providedProduct.setPool(newPool);
                    subProvidedProducts.add(providedProduct);
                }
            }

            helper.copyProductAttributesOntoPool(sub.getProduct().getId(), newPool);
            if (sub.getDerivedProduct() != null) {
                newPool.setDerivedProductId(sub.getDerivedProduct().getId());
                newPool.setDerivedProductName(sub.getDerivedProduct().getName());
                helper.copySubProductAttributesOntoPool(sub.getDerivedProduct().getId(),
                    newPool);
            }

            for (Branding b : sub.getBranding()) {
                newPool.getBranding().add(new Branding(b.getProductId(), b.getType(),
                    b.getName()));
            }

            newPool.setSourceSubscription(new SourceSubscription(sub.getId(), "master"));
            ProductAttribute virtAtt = sub.getProduct().getAttribute("virt_only");

            // note: the product attributes are getting copied above, but the following will
            // make virt_only a pool attribute. That makes the pool explicitly virt_only to
            // subscription manager and any other downstream comsumer.
            if (virtAtt != null && virtAtt.getValue() != null &&
                !virtAtt.getValue().equals("")) {
                newPool.addAttribute(new org.candlepin.model.PoolAttribute("virt_only",
                    virtAtt.getValue()));
            }

            pools.add(newPool);
        }

        boolean hostLimited = attributes.containsKey("host_limited") &&
            attributes.get("host_limited").equals("true");
        // Check if we need to create a virt-only pool for this subscription:
        if (attributes.containsKey("virt_limit") && !config.getBoolean(ConfigProperties.STANDALONE) &&
            !hostLimited && !hasBonusPool(existingPools)) {
            HashMap<String, String> virtAttributes = new HashMap<String, String>();
            virtAttributes.put("virt_only", "true");
            virtAttributes.put("pool_derived", "true");
            virtAttributes.put("physical_only", "false");
            // Make sure the virt pool does not have a virt_limit,
            // otherwise this will recurse infinitely
            virtAttributes.put("virt_limit", "0");

            String virtQuantity = getVirtQuantity(attributes.get("virt_limit"), quantity);
            if (virtQuantity != null) {
                Pool derivedPool = helper.createPool(sub, sub.getProduct().getId(),
                                                    virtQuantity, virtAttributes);
                // Using derived here because only one derived pool
                // is created for this subscription
                derivedPool.setSourceSubscription(
                    new SourceSubscription(sub.getId(), "derived"));
                pools.add(derivedPool);
            }
        }
        return pools;
    }

    private boolean hasMasterPool(List<Pool> pools) {
        if (pools != null) {
            for (Pool p : pools) {
                if (p.getSourceSubscription().getSubscriptionSubKey().equals("master")) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean hasBonusPool(List<Pool> pools) {
        if (pools != null) {
            for (Pool p : pools) {
                if (p.getSourceSubscription().getSubscriptionSubKey().equals("derived")) {
                    return true;
                }
            }
        }
        return false;
    }

    /*
     * Returns null if invalid
     */
    private String getVirtQuantity(String virtLimit, long quantity) {
        if ("unlimited".equals(virtLimit)) {
            return virtLimit;
        }
        try {
            int virtLimitInt = Integer.parseInt(virtLimit);
            if (virtLimitInt > 0) {
                return String.valueOf(virtLimitInt * quantity);
            }
        }
        catch (NumberFormatException nfe) {
            // Nothing to update if we get here.
            log.warn("Invalid virt_limit attribute specified.");
        }
        return null;
    }

    /**
     * Refresh pools which have no subscription tied (directly) to them.
     *
     * @param floatingPools ools with no subscription ID
     * @return pool updates
     */
    public List<PoolUpdate> updatePools(List<Pool> floatingPools) {
        List<PoolUpdate> updates = new LinkedList<PoolUpdate>();
        for (Pool p : floatingPools) {

            if (p.getSubscriptionId() != null) {
                // Should be filtered out before calling, but just in case we skip it:
                continue;
            }

            if (p.getSourceStack() != null) {
                Consumer c = p.getSourceStack().getSourceConsumer();
                if (c == null) {
                    log.error("Stack derived pool has no source consumer: " + p.getId());
                }
                else {
                    PoolUpdate update = updatePoolFromStack(p);
                    if (update.changed()) {
                        updates.add(update);
                    }
                }
            }
        }
        return updates;
    }

    public List<PoolUpdate> updatePools(Subscription sub, List<Pool> existingPools) {
        log.debug("Refreshing pools for existing subscription: " + sub);
        log.debug("  existing pools: " + existingPools.size());
        PoolHelper helper = new PoolHelper(this.poolManager, this.productCache, null);

        List<PoolUpdate> poolsUpdated = new LinkedList<PoolUpdate>();
        Map<String, String> attributes =
            helper.getFlattenedAttributes(sub.getProduct());
        for (Pool existingPool : existingPools) {

            log.debug("Checking pool: " + existingPool.getId());

            // Used to track if anything has changed:
            PoolUpdate update = new PoolUpdate(existingPool);

            update.setDatesChanged(checkForDateChange(sub.getStartDate(),
                sub.getEndDate(), existingPool));

            update.setQuantityChanged(
                checkForQuantityChange(sub, existingPool, existingPools, attributes));

            if (!existingPool.isMarkedForDelete()) {
                // Checks product name, ID, and provided products. Attributes are handled
                // separately.
                // TODO: should they be separate? ^^
                update.setProductsChanged(
                    checkForChangedProducts(sub.getProduct().getId(),
                        sub.getProduct().getName(),
                        getExpectedProvidedProducts(sub, existingPool),
                        existingPool));

                update.setDerivedProductsChanged(
                    checkForChangedDerivedProducts(sub, existingPool));

                update.setProductAttributesChanged(checkForProductAttributeChanges(sub,
                    helper, existingPool));

                update.setDerivedProductAttributesChanged(
                    checkForSubProductAttributeChanges(sub, helper, existingPool));

                update.setOrderChanged(checkForOrderDataChanges(sub, helper,
                    existingPool));

                update.setBrandingChanged(checkForBrandingChanges(sub, existingPool));
            }
            // All done, see if we found any changes and return an update object if so:
            if (update.changed()) {
                poolsUpdated.add(update);
            }
            else {
                log.debug("   No updates required.");
            }
        }

        return poolsUpdated;
    }

    /**
     * Updates the pool based on the entitlements in the specified stack.
     * @param pool
     * @param consumer
     * @param stackId
     *
     * @return pool update specifics
     */
    public PoolUpdate updatePoolFromStack(Pool pool) {
        List<Entitlement> stackedEnts = this.entCurator.findByStackId(
            pool.getSourceConsumer(), pool.getSourceStackId());
        return this.updatePoolFromStackedEntitlements(pool, stackedEnts);
    }

    public PoolUpdate updatePoolFromStackedEntitlements(Pool pool,
            List<Entitlement> stackedEnts) {

        PoolUpdate update = new PoolUpdate(pool);

        // Nothing to do if there were no entitlements found.
        if (stackedEnts.isEmpty()) {
            return update;
        }

        pool.setSourceEntitlement(null);
        pool.setSourceSubscription(null);

        StackedSubPoolValueAccumulator acc =
            new StackedSubPoolValueAccumulator(pool, stackedEnts);

        // Check if the quantity should be changed. If there was no
        // virt limiting entitlement, then we leave the quantity alone,
        // else, we set the quantity to that of the eldest virt limiting
        // entitlement pool.
        Entitlement eldestWithVirtLimit = acc.getEldestWithVirtLimit();
        if (eldestWithVirtLimit != null) {
            // Quantity may have changed, lets see.
            String virtLimit =
                eldestWithVirtLimit.getPool().getProductAttributeValue("virt_limit");

            Long quantity = virtLimit.equalsIgnoreCase("unlimited") ?
                -1L : Long.parseLong(virtLimit);

            if (!quantity.equals(pool.getQuantity())) {
                pool.setQuantity(quantity);
                update.setQuantityChanged(true);
            }
        }

        update.setDatesChanged(checkForDateChange(acc.getStartDate(), acc.getEndDate(),
            pool));

        // We use the "oldest" entitlement as the master for determining values that
        // could have come from the various subscriptions.
        Entitlement eldest = acc.getEldest();
        Pool eldestEntPool = eldest.getPool();
        boolean useDerived = eldestEntPool.getDerivedProductId() != null;
        String prodId = useDerived ?
            eldestEntPool.getDerivedProductId() : eldestEntPool.getProductId();
        String prodName = useDerived ?
            eldestEntPool.getDerivedProductName() : eldestEntPool.getProductName();

        // Check if product ID, name, or provided products have changed.
        update.setProductsChanged(checkForChangedProducts(prodId, prodName,
            acc.getExpectedProvidedProds(), pool));

        // Check if product attributes have changed.
        Set<ProductPoolAttribute> expectedAttrs = acc.getExpectedAttributes();
        if (!pool.getProductAttributes().equals(
            new HashSet<ProductPoolAttribute>(expectedAttrs))) {
            // Make sure each attribute has correct product ID on it,
            // and update the pool.
            pool.getProductAttributes().clear();
            for (ProductPoolAttribute attr : expectedAttrs) {
                attr.setProductId(pool.getProductId());
                pool.addProductAttribute(attr);
            }
            update.setProductAttributesChanged(true);
        }

        if (!StringUtils.equals(eldestEntPool.getContractNumber(),
            pool.getContractNumber()) ||
            !StringUtils.equals(eldestEntPool.getOrderNumber(), pool.getOrderNumber()) ||
            !StringUtils.equals(eldestEntPool.getAccountNumber(),
                pool.getAccountNumber())) {

            pool.setContractNumber(eldestEntPool.getContractNumber());
            pool.setAccountNumber(eldestEntPool.getAccountNumber());
            pool.setOrderNumber(eldestEntPool.getOrderNumber());
            update.setOrderChanged(true);
        }

        // If there are any changes made, then mark all the entitlements as dirty
        // so that they get regenerated on next checkin.
        if (update.changed()) {
            for (Entitlement ent : pool.getEntitlements()) {
                ent.setDirty(true);
            }
        }
        return update;
    }

    private boolean checkForOrderDataChanges(Subscription sub,
        PoolHelper helper, Pool existingPool) {
        boolean orderDataChanged = helper.checkForOrderChanges(existingPool, sub);
        if (orderDataChanged) {
            existingPool.setAccountNumber(sub.getAccountNumber());
            existingPool.setOrderNumber(sub.getOrderNumber());
            existingPool.setContractNumber(sub.getContractNumber());
        }
        return orderDataChanged;
    }

    private boolean checkForProductAttributeChanges(Subscription sub,
        PoolHelper helper, Pool existingPool) {
        boolean prodAttrsChanged = false;
        // Transfer the subscription's sub-product attributes instead if applicable:
        if (existingPool.hasAttribute("pool_derived") && sub.getDerivedProduct() != null) {
            prodAttrsChanged = helper.copyProductAttributesOntoPool(
                sub.getDerivedProduct().getId(), existingPool);
        }
        else {
            prodAttrsChanged = helper.copyProductAttributesOntoPool(
                sub.getProduct().getId(), existingPool);
        }

        return prodAttrsChanged;
    }

    private boolean checkForSubProductAttributeChanges(Subscription sub,
        PoolHelper helper, Pool existingPool) {
        boolean subProdAttrsChanged = false;
        if (!existingPool.hasAttribute("pool_derived") && sub.getDerivedProduct() != null) {
            subProdAttrsChanged = helper.copySubProductAttributesOntoPool(
                sub.getDerivedProduct().getId(), existingPool);
        }
        return subProdAttrsChanged;
    }

    private boolean checkForBrandingChanges(Subscription sub, Pool existingPool) {
        boolean brandingChanged = false;

        if (sub.getBranding().size() != existingPool.getBranding().size()) {
            brandingChanged = true;
        }
        else {
            for (Branding b : sub.getBranding()) {
                if (!existingPool.getBranding().contains(b)) {
                    syncBranding(sub, existingPool);
                    brandingChanged = true;
                    break;
                }
            }
        }

        if (brandingChanged) {
            syncBranding(sub, existingPool);
        }
        return brandingChanged;
    }

    /*
     * Something has changed, sync the branding.
     */
    private void syncBranding(Subscription sub, Pool pool) {
        pool.getBranding().clear();
        for (Branding b : sub.getBranding()) {
            pool.getBranding().add(new Branding(b.getProductId(), b.getType(),
                b.getName()));
        }
    }

    private Set<ProvidedProduct> getExpectedProvidedProducts(Subscription sub,
        Pool existingPool) {
        Set<ProvidedProduct> incomingProvided = new HashSet<ProvidedProduct>();
        if (sub.getProvidedProducts() != null) {
            for (Product p : sub.getProvidedProducts()) {
                incomingProvided.add(new ProvidedProduct(p.getId(), p.getName(),
                    existingPool));
            }
        }
        return incomingProvided;
    }

    private boolean checkForChangedProducts(String incomingProductId,
        String incomingProductName, Set<ProvidedProduct> incomingProvided,
        Pool existingPool) {

        boolean productsChanged =
            !incomingProductId.equals(existingPool.getProductId());
        productsChanged = productsChanged ||
            !incomingProductName.equals(existingPool.getProductName());

        // Build expected set of ProvidedProducts and compare:
        Set<ProvidedProduct> currentProvided = existingPool.getProvidedProducts();
        productsChanged = productsChanged || !currentProvided.equals(incomingProvided);

        if (productsChanged) {
            existingPool.setProductId(incomingProductId);
            existingPool.setProductName(incomingProductName);
            existingPool.getProvidedProducts().clear();
            existingPool.getProvidedProducts().addAll(incomingProvided);
        }
        return productsChanged;
    }

    private boolean checkForChangedDerivedProducts(Subscription sub,
        Pool existingPool) {

        boolean productsChanged = false;
        if (sub.getDerivedProduct() != null) {
            productsChanged = !sub.getDerivedProduct().getId().equals(
                existingPool.getDerivedProductId());
            productsChanged = productsChanged ||
                !sub.getDerivedProduct().getName().equals(
                    existingPool.getDerivedProductName());
        }

        // Build expected set of ProvidedProducts and compare:
        Set<DerivedProvidedProduct> currentProvided =
            existingPool.getDerivedProvidedProducts();
        Set<DerivedProvidedProduct> incomingProvided =
            new HashSet<DerivedProvidedProduct>();
        if (sub.getDerivedProvidedProducts() != null) {
            for (Product p : sub.getDerivedProvidedProducts()) {
                incomingProvided.add(new DerivedProvidedProduct(p.getId(), p.getName(),
                    existingPool));
            }
        }
        productsChanged = productsChanged || !currentProvided.equals(incomingProvided);

        if (productsChanged) {
            // 998317: NPE during refresh causes refresh to abort.
            // Above we check getDerivedProduct for null, but here
            // we ignore the fact that it may be null. So we will
            // now check for null to avoid blowing up.
            if (sub.getDerivedProduct() != null) {
                existingPool.setDerivedProductName(sub.getDerivedProduct().getName());
                existingPool.setDerivedProductId(sub.getDerivedProduct().getId());
            }
            else {
                // subscription no longer has a derived product
                existingPool.setDerivedProductName(null);
                existingPool.setDerivedProductId(null);
            }
            existingPool.getDerivedProvidedProducts().clear();
            if (incomingProvided != null && !incomingProvided.isEmpty()) {
                existingPool.getDerivedProvidedProducts().addAll(incomingProvided);
            }
        }
        return productsChanged;
    }

    private boolean checkForDateChange(Date start, Date end, Pool existingPool) {

        boolean datesChanged = (!start.equals(
            existingPool.getStartDate())) ||
            (!end.equals(existingPool.getEndDate()));

        if (datesChanged) {
            existingPool.setStartDate(start);
            existingPool.setEndDate(end);
        }
        return datesChanged;
    }

    private boolean checkForQuantityChange(Subscription sub, Pool existingPool,
        List<Pool> existingPools, Map<String, String> attributes) {

        // Expected quantity is normally the subscription's quantity, but for
        // virt only pools we expect it to be sub quantity * virt_limit:
        long expectedQuantity = calculateQuantity(sub);
        expectedQuantity = processVirtLimitPools(existingPools,
            attributes, existingPool, expectedQuantity);


        boolean quantityChanged = !(expectedQuantity == existingPool.getQuantity());

        if (quantityChanged) {
            existingPool.setQuantity(expectedQuantity);
        }

        return quantityChanged;
    }

    private long processVirtLimitPools(List<Pool> existingPools,
        Map<String, String> attributes, Pool existingPool, long expectedQuantity) {
        /*
         *  WARNING: when updating pools, we have the added complication of having to
         *  watch out for pools that candlepin creates internally. (i.e. virt bonus
         *  pools in hosted (created when sub is first detected), and host restricted
         *  virt pools when on-site. (created when a host binds)
         */
        if (existingPool.hasAttribute("pool_derived") &&
            existingPool.attributeEquals("virt_only", "true") &&
            existingPool.hasProductAttribute("virt_limit")) {

            if (!attributes.containsKey("virt_limit")) {
                log.warn("virt_limit attribute has been removed from subscription, " +
                    "flagging pool for deletion if supported: " + existingPool.getId());
                // virt_limit has been removed! We need to clean up this pool. Set
                // attribute to notify the server of this:
                existingPool.setMarkedForDelete(true);
                // Older candlepin's won't look at the delete indicator, so we will
                // set the expected quantity to 0 to effectively disable the pool
                // on those servers as well.
                expectedQuantity = 0;
            }
            else {
                String virtLimitStr = attributes.get("virt_limit");

                if ("unlimited".equals(virtLimitStr)) {
                    // 0 will only happen if the rules set it to be 0 -- don't modify
                    // -1 for pretty much all the rest
                    expectedQuantity = existingPool.getQuantity() == 0 ?
                        0 : -1;
                }
                else {
                    try {
                        int virtLimit = Integer.parseInt(virtLimitStr);
                        if (config.getBoolean(ConfigProperties.STANDALONE)) {
                            // this is how we determined the quantity
                            expectedQuantity = virtLimit;
                        }
                        else {
                            // we need to see if a parent pool exists and has been
                            // exported. Adjust is number exported from a parent pool.
                            // If no parent pool, adjust = 0 [a scenario of virtual pool
                            // only]
                            //
                            // WARNING: we're assuming there is only one base
                            // (non-derived) pool. This may change in the future
                            // requiring a more complex
                            // adjustment for exported quantities if there are multiple
                            // pools in play.
                            long adjust = 0L;
                            for (Pool derivedPool : existingPools) {
                                String isDerived =
                                    derivedPool.getAttributeValue("pool_derived");
                                if (isDerived == null) {
                                    adjust = derivedPool.getExported();
                                }
                            }
                            expectedQuantity = (expectedQuantity - adjust) * virtLimit;
                        }
                    }
                    catch (NumberFormatException nfe) {
                        // Nothing to update if we get here.
//                            continue;
                    }
                }
            }
        }
        return expectedQuantity;
    }

}
TOP

Related Classes of org.candlepin.policy.js.pool.PoolRules

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.