// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.allocator;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import javax.inject.Inject;

import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.cloud.configuration.Config;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.deploy.DataCenterDeployment;
import com.cloud.deploy.DeploymentPlan;
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.org.Cluster.ClusterType;
import com.cloud.org.Managed.ManagedState;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.ScopeType;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolStatus;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.db.DB;
import com.cloud.vm.DiskProfile;
import com.cloud.vm.VirtualMachineProfile;

import junit.framework.Assert;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/storageContext.xml")
public class StorageAllocatorTest {
    @Inject
    PrimaryDataStoreDao storagePoolDao;
    @Inject
    StorageManager storageMgr;
    @Inject
    DiskOfferingDao diskOfferingDao;
    @Inject
    VolumeDao volumeDao;
    @Inject
    HostPodDao podDao;
    @Inject
    ClusterDao clusterDao;
    @Inject
    DataCenterDao dcDao;
    @Inject
    StoragePoolDetailsDao poolDetailsDao;
    @Inject
    DataStoreProviderManager providerMgr;
    @Inject
    ConfigurationDao configDao;
    Long dcId = 1l;
    Long podId = 1l;
    Long clusterId = 1l;
    Long volumeId = null;
    Long diskOfferingId = null;
    Long storagePoolId = null;
    VolumeVO volume = null;
    DiskOfferingVO diskOffering = null;
    StoragePoolVO storage = null;

    @Before
    @DB
    public void setup() throws Exception {
        ConfigurationVO cfg = configDao.findByName(Config.VmAllocationAlgorithm.key());
        if (cfg == null) {
            ConfigurationVO configVO = new ConfigurationVO("test", "DEFAULT", "test", Config.VmAllocationAlgorithm.key(), "userdispersing", null);
            configDao.persist(configVO);
        }
        ComponentContext.initComponentsLifeCycle();

    }

    protected void createDb() {
        DataCenterVO dc =
                new DataCenterVO(UUID.randomUUID().toString(), "test", "8.8.8.8", null, "10.0.0.1", null, "10.0.0.1/24", null, null, NetworkType.Basic, null, null, true,
                        true, null, null);
        dc = dcDao.persist(dc);
        dcId = dc.getId();

        HostPodVO pod = new HostPodVO(UUID.randomUUID().toString(), dc.getId(), "255.255.255.255", "", 8, "test");
        pod = podDao.persist(pod);
        podId = pod.getId();

        ClusterVO cluster = new ClusterVO(dc.getId(), pod.getId(), "devcloud cluster");
        cluster.setHypervisorType(HypervisorType.XenServer.toString());
        cluster.setClusterType(ClusterType.CloudManaged);
        cluster.setManagedState(ManagedState.Managed);
        cluster = clusterDao.persist(cluster);
        clusterId = cluster.getId();

        DataStoreProvider provider = providerMgr.getDataStoreProvider(DataStoreProvider.DEFAULT_PRIMARY);
        storage = new StoragePoolVO();
        storage.setDataCenterId(dcId);
        storage.setPodId(podId);
        storage.setPoolType(StoragePoolType.NetworkFilesystem);
        storage.setClusterId(clusterId);
        storage.setStatus(StoragePoolStatus.Up);
        storage.setScope(ScopeType.CLUSTER);
        storage.setUsedBytes(1000);
        storage.setCapacityBytes(20000);
        storage.setHostAddress(UUID.randomUUID().toString());
        storage.setPath(UUID.randomUUID().toString());
        storage.setStorageProviderName(provider.getName());
        storage = storagePoolDao.persist(storage);
        storagePoolId = storage.getId();

        storageMgr.createCapacityEntry(storage.getId());

        diskOffering = new DiskOfferingVO();
        diskOffering.setDiskSize(500);
        diskOffering.setName("test-disk");
        diskOffering.setSystemUse(false);
        diskOffering.setUseLocalStorage(false);
        diskOffering.setCustomized(false);
        diskOffering.setRecreatable(false);
        diskOffering = diskOfferingDao.persist(diskOffering);
        diskOfferingId = diskOffering.getId();

        volume =
                new VolumeVO(Volume.Type.ROOT, "volume", dcId, 1, 1, diskOffering.getId(), Storage.ProvisioningType.THIN, diskOffering.getDiskSize(), diskOffering.getMinIops(),
                        diskOffering.getMaxIops(), "");
        volume = volumeDao.persist(volume);
        volumeId = volume.getId();
    }

    @Inject
    List<StoragePoolAllocator> allocators;

    @Test
    public void testClusterAllocatorMultiplePools() {
        Long newStorageId = null;
        try {
            createDb();

            DataStoreProvider provider = providerMgr.getDataStoreProvider(DataStoreProvider.DEFAULT_PRIMARY);
            storage = new StoragePoolVO();
            storage.setDataCenterId(dcId);
            storage.setPodId(podId);
            storage.setPoolType(StoragePoolType.NetworkFilesystem);
            storage.setClusterId(clusterId);
            storage.setStatus(StoragePoolStatus.Up);
            storage.setScope(ScopeType.CLUSTER);
            storage.setUsedBytes(1000);
            storage.setCapacityBytes(20000);
            storage.setHostAddress(UUID.randomUUID().toString());
            storage.setPath(UUID.randomUUID().toString());
            storage.setStorageProviderName(provider.getName());
            StoragePoolVO newStorage = storagePoolDao.persist(storage);
            newStorageId = newStorage.getId();

            DiskProfile profile = new DiskProfile(volume, diskOffering, HypervisorType.XenServer);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.size(), 1);
                    foundAcct++;
                }
            }

            if (foundAcct > 1 || foundAcct == 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();

            if (newStorageId != null) {
                storagePoolDao.remove(newStorageId);
            }
            Assert.fail();
        }
    }

    @Test
    public void testClusterAllocator() {
        try {
            createDb();
            DiskProfile profile = new DiskProfile(volume, diskOffering, HypervisorType.XenServer);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.get(0).getId(), storage.getId());
                    foundAcct++;
                }
            }

            if (foundAcct > 1 || foundAcct == 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    @Test
    public void testClusterAllocatorWithTags() {
        try {
            createDb();
            StoragePoolDetailVO detailVO = new StoragePoolDetailVO(this.storagePoolId, "high", "true", true);
            poolDetailsDao.persist(detailVO);
            DiskOfferingVO diskOff = this.diskOfferingDao.findById(diskOffering.getId());
            List<String> tags = new ArrayList<String>();
            tags.add("high");
            diskOff.setTagsArray(tags);
            diskOfferingDao.update(diskOff.getId(), diskOff);

            DiskProfile profile = new DiskProfile(volume, diskOff, HypervisorType.XenServer);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.get(0).getId(), storage.getId());
                    foundAcct++;
                }
            }

            if (foundAcct > 1 || foundAcct == 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    @Test
    public void testClusterAllocatorWithWrongTag() {
        try {
            createDb();
            StoragePoolDetailVO detailVO = new StoragePoolDetailVO(this.storagePoolId, "high", "true", true);
            poolDetailsDao.persist(detailVO);
            DiskOfferingVO diskOff = this.diskOfferingDao.findById(diskOffering.getId());
            List<String> tags = new ArrayList<String>();
            tags.add("low");
            diskOff.setTagsArray(tags);
            diskOfferingDao.update(diskOff.getId(), diskOff);

            DiskProfile profile = new DiskProfile(volume, diskOff, HypervisorType.XenServer);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    foundAcct++;
                }
            }

            if (foundAcct != 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    @Test
    public void testZoneWideStorageAllocator() {
        try {
            createDb();

            StoragePoolVO pool = storagePoolDao.findById(storagePoolId);
            pool.setHypervisor(HypervisorType.KVM);
            pool.setScope(ScopeType.ZONE);
            pool.setClusterId(null);
            pool.setPodId(null);
            storagePoolDao.update(pool.getId(), pool);

            DiskProfile profile = new DiskProfile(volume, diskOffering, HypervisorType.KVM);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(vmProfile.getHypervisorType()).thenReturn(HypervisorType.KVM);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            Mockito.when(storageMgr.storagePoolHasEnoughIops(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.get(0).getId(), storage.getId());
                    foundAcct++;
                }
            }

            if (foundAcct > 1 || foundAcct == 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    @Test
    public void testCLOUDSTACK3481() {
        try {
            createDb();

            StoragePoolVO pool = storagePoolDao.findById(storagePoolId);
            pool.setHypervisor(HypervisorType.KVM);
            pool.setScope(ScopeType.ZONE);
            pool.setClusterId(null);
            pool.setPodId(null);
            storagePoolDao.update(pool.getId(), pool);

            DiskProfile profile = new DiskProfile(volume, diskOffering, HypervisorType.KVM);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Account account = Mockito.mock(Account.class);
            Mockito.when(account.getAccountId()).thenReturn(1L);
            Mockito.when(vmProfile.getHypervisorType()).thenReturn(HypervisorType.KVM);
            Mockito.when(vmProfile.getOwner()).thenReturn(account);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            Mockito.when(storageMgr.storagePoolHasEnoughIops(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.get(0).getId(), storage.getId());
                    foundAcct++;
                }
            }

            if (foundAcct > 1 || foundAcct == 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    @Test
    public void testPoolStateIsNotUp() {
        try {
            createDb();

            StoragePoolVO pool = storagePoolDao.findById(storagePoolId);
            pool.setScope(ScopeType.ZONE);
            pool.setStatus(StoragePoolStatus.Maintenance);
            storagePoolDao.update(pool.getId(), pool);

            DiskProfile profile = new DiskProfile(volume, diskOffering, HypervisorType.XenServer);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.get(0).getId(), storage.getId());
                    foundAcct++;
                }
            }

            if (foundAcct == 1) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    @Test
    public void testLocalStorageAllocator() {
        try {
            createDb();

            StoragePoolVO pool = storagePoolDao.findById(storagePoolId);
            pool.setScope(ScopeType.HOST);
            storagePoolDao.update(pool.getId(), pool);

            DiskOfferingVO diskOff = diskOfferingDao.findById(diskOfferingId);
            diskOff.setUseLocalStorage(true);
            diskOfferingDao.update(diskOfferingId, diskOff);

            DiskProfile profile = new DiskProfile(volume, diskOff, HypervisorType.XenServer);
            VirtualMachineProfile vmProfile = Mockito.mock(VirtualMachineProfile.class);
            Mockito.when(storageMgr.storagePoolHasEnoughSpace(Matchers.anyListOf(Volume.class), Matchers.any(StoragePool.class))).thenReturn(true);
            DeploymentPlan plan = new DataCenterDeployment(dcId, podId, clusterId, null, null, null);
            int foundAcct = 0;
            for (StoragePoolAllocator allocator : allocators) {
                List<StoragePool> pools = allocator.allocateToPool(profile, vmProfile, plan, new ExcludeList(), 1);
                if (!pools.isEmpty()) {
                    Assert.assertEquals(pools.get(0).getId(), storage.getId());
                    foundAcct++;
                }
            }

            if (foundAcct > 1 || foundAcct == 0) {
                Assert.fail();
            }
        } catch (Exception e) {
            cleanDb();
            Assert.fail();
        }
    }

    protected void cleanDb() {
        if (volumeId != null) {
            volumeDao.remove(volumeId);
            volumeId = null;
        }
        if (diskOfferingId != null) {
            diskOfferingDao.remove(diskOfferingId);
            diskOfferingId = null;
        }
        if (storagePoolId != null) {
            storagePoolDao.remove(storagePoolId);
            storagePoolId = null;
        }
        if (clusterId != null) {
            clusterDao.remove(clusterId);
            clusterId = null;
        }
        if (podId != null) {
            podDao.remove(podId);
            podId = null;
        }
        if (dcId != null) {
            dcDao.remove(dcId);
            dcId = null;
        }
    }

}
