package org.zstack.storage.primary.local;

import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.header.storage.primary.PrimaryStorageCapacityUpdaterRunnable;
import org.zstack.header.storage.primary.PrimaryStorageCapacityVO;
import org.zstack.header.storage.primary.PrimaryStorageOverProvisioningManager;
import org.zstack.storage.primary.PrimaryStorageCapacityUpdater;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;

import javax.persistence.LockModeType;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by frank on 1/23/2016.
 */
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class LocalStorageCapacityRecalculator {
    private static final CLogger logger = Utils.getLogger(LocalStorageCapacityRecalculator.class);

    @Autowired
    private DatabaseFacade dbf;
    @Autowired
    private PrimaryStorageOverProvisioningManager ratioMgr;

    @Transactional
    public LocalStorageCapacityRecalculator calculateByHostUuids(String psUuid, List<String> huuids) {
        DebugUtils.Assert(!huuids.isEmpty(), "hostUuids cannot be empty");

        Map<String, Long> hostCap = new HashMap<>();

        String sql = "select sum(vol.size), ref.hostUuid" +
                " from VolumeVO vol, LocalStorageResourceRefVO ref" +
                " where vol.primaryStorageUuid = :psUuid" +
                " and vol.uuid = ref.resourceUuid" +
                " and ref.primaryStorageUuid = vol.primaryStorageUuid" +
                " and ref.hostUuid in (:huuids)" +
                " group by ref.hostUuid";
        TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
        q.setParameter("psUuid", psUuid);
        q.setParameter("huuids", huuids);
        List<Tuple> ts = q.getResultList();
        for (Tuple t : ts) {
            if (t.get(0, Long.class) == null) {
                // no volume
                continue;
            }

            long cap = t.get(0, Long.class);
            String hostUuid = t.get(1, String.class);
            hostCap.put(hostUuid, ratioMgr.calculateByRatio(psUuid, cap));
        }

        for (String huuid : huuids) {
            // note: templates in image cache are physical size
            // do not calculate over provisioning for them
            sql = "select sum(i.size)" +
                    " from ImageCacheVO i" +
                    " where i.installUrl like :mark" +
                    " and i.primaryStorageUuid = :psUuid" +
                    " group by i.primaryStorageUuid";
            TypedQuery<Long> iq = dbf.getEntityManager().createQuery(sql, Long.class);
            iq.setParameter("psUuid", psUuid);
            iq.setParameter("mark", String.format("%%hostUuid://%s%%", huuid));
            List<Long> is = iq.getResultList();
            if (!is.isEmpty()) {
                Long isize = is.get(0);
                Long ncap = hostCap.get(huuid);
                ncap = ncap == null ? isize : ncap + isize;
                hostCap.put(huuid, ncap);
            } else {
                // if the host has no volume and image cache
                // set its used capacity to zero
                Long ncap = hostCap.get(huuid);
                ncap = ncap == null ? 0 : ncap;
                hostCap.put(huuid, ncap);
            }
        }

        for (Map.Entry<String, Long> e : hostCap.entrySet()) {
            String hostUuid = e.getKey();
            long used = e.getValue();

            String sqlLocalStorageHostRefVO = "select ref" +
                    " from LocalStorageHostRefVO ref" +
                    " where hostUuid = :hostUuid" +
                    " and primaryStorageUuid = :primaryStorageUuid";
            TypedQuery<LocalStorageHostRefVO> query = dbf.getEntityManager().
                    createQuery(sqlLocalStorageHostRefVO, LocalStorageHostRefVO.class);
            query.setParameter("hostUuid", hostUuid);
            query.setParameter("primaryStorageUuid", psUuid);
            LocalStorageHostRefVO ref = query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getSingleResult();

            long old = ref.getAvailableCapacity();
            long avail = ref.getTotalCapacity() - used - ref.getSystemUsedCapacity();
            ref.setAvailableCapacity(avail);
            dbf.getEntityManager().merge(ref);
            logger.debug(String.format("re-calculated available capacity[before:%s, now: %s] of host[uuid:%s]" +
                            " of the local storage[uuid:%s] with over-provisioning ratio[%s]",
                    old, avail, hostUuid, psUuid, ratioMgr.getRatio(psUuid)));
        }

        return this;
    }

    @Transactional
    public LocalStorageCapacityRecalculator calculateByPrimaryStorageUuid(String psUuid) {
        String sql = "select ref.hostUuid" +
                " from LocalStorageHostRefVO ref" +
                " where ref.primaryStorageUuid = :psUuid";
        TypedQuery<String> hq = dbf.getEntityManager().createQuery(sql, String.class);
        hq.setParameter("psUuid", psUuid);
        List<String> huuids = hq.getResultList();
        if (huuids != null && !huuids.isEmpty()) {
            calculateByHostUuids(psUuid, huuids);
        }
        return this;
    }

    @Transactional
    public LocalStorageCapacityRecalculator calculateTotalCapacity(String psUuid) {
        String sql = "select sum(ref.totalCapacity)," +
                " sum(ref.availableCapacity)," +
                " sum(ref.totalPhysicalCapacity)," +
                " sum(ref.availablePhysicalCapacity)" +
                " from LocalStorageHostRefVO ref" +
                " where ref.primaryStorageUuid = :psUuid";
        TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
        q.setParameter("psUuid", psUuid);
        Tuple ts = q.getSingleResult();

        final long totalCapacity;
        final long availableCapacity;
        final long totalPhysicalCapacity;
        final long availablePhysicalCapacity;
        PrimaryStorageCapacityUpdater pupdater = new PrimaryStorageCapacityUpdater(psUuid);
        if (ts != null) {
            totalCapacity = ts.get(0) == null ? 0 : ts.get(0, Long.class);
            availableCapacity = ts.get(1) == null ? 0 : ts.get(1, Long.class);
            totalPhysicalCapacity = ts.get(2) == null ? 0 : ts.get(2, Long.class);
            availablePhysicalCapacity = ts.get(3) == null ? 0 : ts.get(3, Long.class);
        } else {
            totalCapacity = 0;
            availableCapacity = 0;
            totalPhysicalCapacity = 0;
            availablePhysicalCapacity = 0;
        }
        pupdater.run(new PrimaryStorageCapacityUpdaterRunnable() {
            @Override
            public PrimaryStorageCapacityVO call(PrimaryStorageCapacityVO cap) {
                cap.setTotalCapacity(totalCapacity);
                cap.setAvailableCapacity(availableCapacity);
                cap.setTotalPhysicalCapacity(totalPhysicalCapacity);
                cap.setAvailablePhysicalCapacity(availablePhysicalCapacity);
                return cap;
            }
        });

        return this;
    }
}

@@ -4400,79 +4400,301 @@
  ref
- = query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getSingleResul
+;%0A            List%3CLocalStorageHostRefVO%3E localStorageHostRefVOS = query.setLockMode(LockModeType.PESSIMISTIC_WRITE).getResultList();%0A            if(localStorageHostRefVOS.size() %3E 0)%7B%0A                ref = localStorageHostRefVOS.ge
 t(
+0
 );
+%0A            %7Delse%7B%0A                break;%0A            %7D
 %0A%0A  

fix bug 2268