mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
3970 lines
182 KiB
Java
Executable File
3970 lines
182 KiB
Java
Executable File
// 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 com.cloud.storage;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.net.Inet6Address;
|
|
import java.net.InetAddress;
|
|
import java.net.URI;
|
|
import java.net.URISyntaxException;
|
|
import java.net.UnknownHostException;
|
|
import java.sql.PreparedStatement;
|
|
import java.sql.ResultSet;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Date;
|
|
import java.util.Enumeration;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import javax.ejb.Local;
|
|
import javax.inject.Inject;
|
|
import javax.naming.ConfigurationException;
|
|
|
|
import org.apache.cloudstack.api.command.admin.storage.*;
|
|
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
|
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
|
|
import org.apache.log4j.Logger;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
import com.cloud.agent.AgentManager;
|
|
import com.cloud.agent.api.Answer;
|
|
import com.cloud.agent.api.BackupSnapshotCommand;
|
|
import com.cloud.agent.api.CleanupSnapshotBackupCommand;
|
|
import com.cloud.agent.api.Command;
|
|
import com.cloud.agent.api.CreateStoragePoolCommand;
|
|
import com.cloud.agent.api.CreateVolumeFromSnapshotAnswer;
|
|
import com.cloud.agent.api.CreateVolumeFromSnapshotCommand;
|
|
import com.cloud.agent.api.DeleteStoragePoolCommand;
|
|
import com.cloud.agent.api.ManageSnapshotCommand;
|
|
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
|
import com.cloud.agent.api.ModifyStoragePoolCommand;
|
|
import com.cloud.agent.api.UpgradeSnapshotCommand;
|
|
import com.cloud.agent.api.storage.CopyVolumeAnswer;
|
|
import com.cloud.agent.api.storage.CopyVolumeCommand;
|
|
import com.cloud.agent.api.storage.CreateAnswer;
|
|
import com.cloud.agent.api.storage.CreateCommand;
|
|
import com.cloud.agent.api.storage.DeleteTemplateCommand;
|
|
import com.cloud.agent.api.storage.DeleteVolumeCommand;
|
|
import com.cloud.agent.api.storage.DestroyCommand;
|
|
import com.cloud.agent.api.to.StorageFilerTO;
|
|
import com.cloud.agent.api.to.VolumeTO;
|
|
import com.cloud.agent.manager.Commands;
|
|
import com.cloud.alert.AlertManager;
|
|
import com.cloud.api.ApiDBUtils;
|
|
import org.apache.cloudstack.api.command.admin.storage.CreateStoragePoolCmd;
|
|
import com.cloud.async.AsyncJobManager;
|
|
import com.cloud.capacity.Capacity;
|
|
import com.cloud.capacity.CapacityManager;
|
|
import com.cloud.capacity.CapacityState;
|
|
import com.cloud.capacity.CapacityVO;
|
|
import com.cloud.capacity.dao.CapacityDao;
|
|
import com.cloud.cluster.CheckPointManager;
|
|
import com.cloud.cluster.ClusterManagerListener;
|
|
import com.cloud.cluster.ManagementServerHostVO;
|
|
import com.cloud.configuration.Config;
|
|
import com.cloud.configuration.ConfigurationManager;
|
|
import com.cloud.configuration.Resource.ResourceType;
|
|
import com.cloud.configuration.dao.ConfigurationDao;
|
|
import com.cloud.consoleproxy.ConsoleProxyManager;
|
|
import com.cloud.dc.ClusterVO;
|
|
import com.cloud.dc.DataCenterVO;
|
|
import com.cloud.dc.HostPodVO;
|
|
import com.cloud.dc.Pod;
|
|
import com.cloud.dc.dao.ClusterDao;
|
|
import com.cloud.dc.dao.DataCenterDao;
|
|
import com.cloud.dc.dao.HostPodDao;
|
|
import com.cloud.deploy.DeployDestination;
|
|
import com.cloud.domain.Domain;
|
|
import com.cloud.domain.dao.DomainDao;
|
|
import com.cloud.event.ActionEvent;
|
|
import com.cloud.event.EventTypes;
|
|
import com.cloud.event.UsageEventVO;
|
|
import com.cloud.event.dao.EventDao;
|
|
import com.cloud.event.dao.UsageEventDao;
|
|
import com.cloud.exception.AgentUnavailableException;
|
|
import com.cloud.exception.ConcurrentOperationException;
|
|
import com.cloud.exception.DiscoveryException;
|
|
import com.cloud.exception.InsufficientCapacityException;
|
|
import com.cloud.exception.InsufficientStorageCapacityException;
|
|
import com.cloud.exception.InvalidParameterValueException;
|
|
import com.cloud.exception.OperationTimedoutException;
|
|
import com.cloud.exception.PermissionDeniedException;
|
|
import com.cloud.exception.ResourceAllocationException;
|
|
import com.cloud.exception.ResourceInUseException;
|
|
import com.cloud.exception.ResourceUnavailableException;
|
|
import com.cloud.exception.StorageUnavailableException;
|
|
import com.cloud.host.Host;
|
|
import com.cloud.host.HostVO;
|
|
import com.cloud.host.Status;
|
|
import com.cloud.host.dao.HostDao;
|
|
import com.cloud.hypervisor.Hypervisor.HypervisorType;
|
|
import com.cloud.hypervisor.HypervisorGuruManager;
|
|
import com.cloud.network.NetworkManager;
|
|
import com.cloud.offering.ServiceOffering;
|
|
import com.cloud.org.Grouping;
|
|
import com.cloud.org.Grouping.AllocationState;
|
|
import com.cloud.resource.ResourceManager;
|
|
import com.cloud.resource.ResourceState;
|
|
import com.cloud.server.ManagementServer;
|
|
import com.cloud.server.StatsCollector;
|
|
import com.cloud.service.ServiceOfferingVO;
|
|
import com.cloud.service.dao.ServiceOfferingDao;
|
|
import com.cloud.storage.Storage.ImageFormat;
|
|
import com.cloud.storage.Storage.StoragePoolType;
|
|
import com.cloud.storage.Volume.Event;
|
|
import com.cloud.storage.Volume.Type;
|
|
import com.cloud.storage.allocator.StoragePoolAllocator;
|
|
import com.cloud.storage.dao.DiskOfferingDao;
|
|
import com.cloud.storage.dao.SnapshotDao;
|
|
import com.cloud.storage.dao.SnapshotPolicyDao;
|
|
import com.cloud.storage.dao.StoragePoolDao;
|
|
import com.cloud.storage.dao.StoragePoolHostDao;
|
|
import com.cloud.storage.dao.StoragePoolWorkDao;
|
|
import com.cloud.storage.dao.VMTemplateDao;
|
|
import com.cloud.storage.dao.VMTemplateHostDao;
|
|
import com.cloud.storage.dao.VMTemplatePoolDao;
|
|
import com.cloud.storage.dao.VMTemplateS3Dao;
|
|
import com.cloud.storage.dao.VMTemplateSwiftDao;
|
|
import com.cloud.storage.dao.VolumeDao;
|
|
import com.cloud.storage.dao.VolumeHostDao;
|
|
import com.cloud.storage.download.DownloadMonitor;
|
|
import com.cloud.storage.listener.StoragePoolMonitor;
|
|
import com.cloud.storage.s3.S3Manager;
|
|
import com.cloud.storage.secondary.SecondaryStorageVmManager;
|
|
import com.cloud.storage.snapshot.SnapshotManager;
|
|
import com.cloud.storage.snapshot.SnapshotScheduler;
|
|
import com.cloud.tags.dao.ResourceTagDao;
|
|
import com.cloud.template.TemplateManager;
|
|
import com.cloud.user.Account;
|
|
import com.cloud.user.AccountManager;
|
|
import com.cloud.user.ResourceLimitService;
|
|
import com.cloud.user.User;
|
|
import com.cloud.user.UserContext;
|
|
import com.cloud.user.dao.AccountDao;
|
|
import com.cloud.user.dao.UserDao;
|
|
import com.cloud.uservm.UserVm;
|
|
import com.cloud.utils.EnumUtils;
|
|
import com.cloud.utils.NumbersUtil;
|
|
import com.cloud.utils.Pair;
|
|
import com.cloud.utils.UriUtils;
|
|
import com.cloud.utils.component.Adapters;
|
|
import com.cloud.utils.component.ComponentLocator;
|
|
import com.cloud.utils.component.Manager;
|
|
import com.cloud.utils.concurrency.NamedThreadFactory;
|
|
import com.cloud.utils.db.DB;
|
|
import com.cloud.utils.db.GenericSearchBuilder;
|
|
import com.cloud.utils.db.GlobalLock;
|
|
import com.cloud.utils.db.JoinBuilder;
|
|
import com.cloud.utils.db.JoinBuilder.JoinType;
|
|
import com.cloud.utils.db.SearchBuilder;
|
|
import com.cloud.utils.db.SearchCriteria;
|
|
import com.cloud.utils.db.SearchCriteria.Op;
|
|
import com.cloud.utils.db.Transaction;
|
|
import com.cloud.utils.exception.CloudRuntimeException;
|
|
import com.cloud.utils.exception.ExecutionException;
|
|
import com.cloud.utils.fsm.NoTransitionException;
|
|
import com.cloud.utils.fsm.StateMachine2;
|
|
import com.cloud.vm.ConsoleProxyVO;
|
|
import com.cloud.vm.DiskProfile;
|
|
import com.cloud.vm.DomainRouterVO;
|
|
import com.cloud.vm.SecondaryStorageVmVO;
|
|
import com.cloud.vm.UserVmManager;
|
|
import com.cloud.vm.UserVmVO;
|
|
import com.cloud.vm.VMInstanceVO;
|
|
import com.cloud.vm.VirtualMachine;
|
|
import com.cloud.vm.VirtualMachine.State;
|
|
import com.cloud.vm.VirtualMachineManager;
|
|
import com.cloud.vm.VirtualMachineProfile;
|
|
import com.cloud.vm.VirtualMachineProfileImpl;
|
|
import com.cloud.vm.dao.ConsoleProxyDao;
|
|
import com.cloud.vm.dao.DomainRouterDao;
|
|
import com.cloud.vm.dao.SecondaryStorageVmDao;
|
|
import com.cloud.vm.dao.UserVmDao;
|
|
import com.cloud.vm.dao.VMInstanceDao;
|
|
|
|
@Component
|
|
@Local(value = { StorageManager.class, StorageService.class })
|
|
public class StorageManagerImpl implements StorageManager, Manager, ClusterManagerListener {
|
|
private static final Logger s_logger = Logger.getLogger(StorageManagerImpl.class);
|
|
|
|
protected String _name;
|
|
@Inject
|
|
protected UserVmManager _userVmMgr;
|
|
@Inject
|
|
protected AgentManager _agentMgr;
|
|
@Inject
|
|
protected TemplateManager _tmpltMgr;
|
|
@Inject
|
|
protected AsyncJobManager _asyncMgr;
|
|
@Inject
|
|
protected SnapshotManager _snapshotMgr;
|
|
@Inject
|
|
protected SnapshotScheduler _snapshotScheduler;
|
|
@Inject
|
|
protected AccountManager _accountMgr;
|
|
@Inject
|
|
protected ConfigurationManager _configMgr;
|
|
@Inject
|
|
protected ConsoleProxyManager _consoleProxyMgr;
|
|
@Inject
|
|
protected SecondaryStorageVmManager _secStorageMgr;
|
|
@Inject
|
|
protected NetworkManager _networkMgr;
|
|
@Inject
|
|
protected VolumeDao _volsDao;
|
|
@Inject
|
|
protected HostDao _hostDao;
|
|
@Inject
|
|
protected ConsoleProxyDao _consoleProxyDao;
|
|
@Inject
|
|
protected SnapshotDao _snapshotDao;
|
|
@Inject
|
|
protected SnapshotManager _snapMgr;
|
|
@Inject
|
|
protected SnapshotPolicyDao _snapshotPolicyDao;
|
|
@Inject
|
|
protected StoragePoolHostDao _storagePoolHostDao;
|
|
@Inject
|
|
protected AlertManager _alertMgr;
|
|
@Inject
|
|
protected VMTemplateHostDao _vmTemplateHostDao = null;
|
|
@Inject
|
|
protected VMTemplatePoolDao _vmTemplatePoolDao = null;
|
|
@Inject
|
|
protected VMTemplateSwiftDao _vmTemplateSwiftDao = null;
|
|
@Inject
|
|
protected VMTemplateS3Dao _vmTemplateS3Dao;
|
|
@Inject
|
|
protected S3Manager _s3Mgr;
|
|
@Inject
|
|
protected VMTemplateDao _vmTemplateDao = null;
|
|
@Inject
|
|
protected StoragePoolHostDao _poolHostDao = null;
|
|
@Inject
|
|
protected UserVmDao _userVmDao;
|
|
@Inject
|
|
VolumeHostDao _volumeHostDao;
|
|
@Inject
|
|
protected VMInstanceDao _vmInstanceDao;
|
|
@Inject
|
|
protected StoragePoolDao _storagePoolDao = null;
|
|
@Inject
|
|
protected CapacityDao _capacityDao;
|
|
@Inject
|
|
protected CapacityManager _capacityMgr;
|
|
@Inject
|
|
protected DiskOfferingDao _diskOfferingDao;
|
|
@Inject
|
|
protected AccountDao _accountDao;
|
|
@Inject
|
|
protected EventDao _eventDao = null;
|
|
@Inject
|
|
protected DataCenterDao _dcDao = null;
|
|
@Inject
|
|
protected HostPodDao _podDao = null;
|
|
@Inject
|
|
protected VMTemplateDao _templateDao;
|
|
@Inject
|
|
protected VMTemplateHostDao _templateHostDao;
|
|
@Inject
|
|
protected ServiceOfferingDao _offeringDao;
|
|
@Inject
|
|
protected DomainDao _domainDao;
|
|
@Inject
|
|
protected UserDao _userDao;
|
|
@Inject
|
|
protected ClusterDao _clusterDao;
|
|
@Inject
|
|
protected UsageEventDao _usageEventDao;
|
|
@Inject
|
|
protected VirtualMachineManager _vmMgr;
|
|
@Inject
|
|
protected DomainRouterDao _domrDao;
|
|
@Inject
|
|
protected SecondaryStorageVmDao _secStrgDao;
|
|
@Inject
|
|
protected StoragePoolWorkDao _storagePoolWorkDao;
|
|
@Inject
|
|
protected HypervisorGuruManager _hvGuruMgr;
|
|
@Inject
|
|
protected VolumeDao _volumeDao;
|
|
@Inject
|
|
protected OCFS2Manager _ocfs2Mgr;
|
|
@Inject
|
|
protected ResourceLimitService _resourceLimitMgr;
|
|
@Inject
|
|
protected SecondaryStorageVmManager _ssvmMgr;
|
|
@Inject
|
|
protected ResourceManager _resourceMgr;
|
|
@Inject
|
|
protected CheckPointManager _checkPointMgr;
|
|
@Inject
|
|
protected DownloadMonitor _downloadMonitor;
|
|
@Inject
|
|
protected ResourceTagDao _resourceTagDao;
|
|
@Inject
|
|
protected List<StoragePoolAllocator> _storagePoolAllocators;
|
|
@Inject ConfigurationDao _configDao;
|
|
@Inject ManagementServer _msServer;
|
|
|
|
// TODO : we don't have any instantiated pool discover, disable injection temporarily
|
|
// @Inject
|
|
protected List<StoragePoolDiscoverer> _discoverers;
|
|
|
|
|
|
protected SearchBuilder<VMTemplateHostVO> HostTemplateStatesSearch;
|
|
protected GenericSearchBuilder<StoragePoolHostVO, Long> UpHostsInPoolSearch;
|
|
protected SearchBuilder<VMInstanceVO> StoragePoolSearch;
|
|
protected SearchBuilder<StoragePoolVO> LocalStorageSearch;
|
|
|
|
ScheduledExecutorService _executor = null;
|
|
boolean _storageCleanupEnabled;
|
|
boolean _templateCleanupEnabled = true;
|
|
int _storageCleanupInterval;
|
|
private int _createVolumeFromSnapshotWait;
|
|
private int _copyvolumewait;
|
|
int _storagePoolAcquisitionWaitSeconds = 1800; // 30 minutes
|
|
protected int _retry = 2;
|
|
protected int _pingInterval = 60; // seconds
|
|
protected int _hostRetry;
|
|
protected BigDecimal _overProvisioningFactor = new BigDecimal(1);
|
|
private long _maxVolumeSizeInGb;
|
|
private long _serverId;
|
|
private StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
|
|
private int _customDiskOfferingMinSize = 1;
|
|
private int _customDiskOfferingMaxSize = 1024;
|
|
private double _storageUsedThreshold = 1.0d;
|
|
private double _storageAllocatedThreshold = 1.0d;
|
|
protected BigDecimal _storageOverprovisioningFactor = new BigDecimal(1);
|
|
|
|
private boolean _recreateSystemVmEnabled;
|
|
|
|
public boolean share(VMInstanceVO vm, List<VolumeVO> vols, HostVO host, boolean cancelPreviousShare) throws StorageUnavailableException {
|
|
|
|
// if pool is in maintenance and it is the ONLY pool available; reject
|
|
List<VolumeVO> rootVolForGivenVm = _volsDao.findByInstanceAndType(vm.getId(), Type.ROOT);
|
|
if (rootVolForGivenVm != null && rootVolForGivenVm.size() > 0) {
|
|
boolean isPoolAvailable = isPoolAvailable(rootVolForGivenVm.get(0).getPoolId());
|
|
if (!isPoolAvailable) {
|
|
throw new StorageUnavailableException("Can not share " + vm, rootVolForGivenVm.get(0).getPoolId());
|
|
}
|
|
}
|
|
|
|
// this check is done for maintenance mode for primary storage
|
|
// if any one of the volume is unusable, we return false
|
|
// if we return false, the allocator will try to switch to another PS if available
|
|
for (VolumeVO vol : vols) {
|
|
if (vol.getRemoved() != null) {
|
|
s_logger.warn("Volume id:" + vol.getId() + " is removed, cannot share on this instance");
|
|
// not ok to share
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ok to share
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public VolumeVO allocateDuplicateVolume(VolumeVO oldVol, Long templateId) {
|
|
VolumeVO newVol = new VolumeVO(oldVol.getVolumeType(), oldVol.getName(), oldVol.getDataCenterId(), oldVol.getDomainId(), oldVol.getAccountId(), oldVol.getDiskOfferingId(), oldVol.getSize());
|
|
if (templateId != null) {
|
|
newVol.setTemplateId(templateId);
|
|
} else {
|
|
newVol.setTemplateId(oldVol.getTemplateId());
|
|
}
|
|
newVol.setDeviceId(oldVol.getDeviceId());
|
|
newVol.setInstanceId(oldVol.getInstanceId());
|
|
newVol.setRecreatable(oldVol.isRecreatable());
|
|
return _volsDao.persist(newVol);
|
|
}
|
|
|
|
private boolean isPoolAvailable(Long poolId) {
|
|
// get list of all pools
|
|
List<StoragePoolVO> pools = _storagePoolDao.listAll();
|
|
|
|
// if no pools or 1 pool which is in maintenance
|
|
if (pools == null || pools.size() == 0 || (pools.size() == 1 && pools.get(0).getStatus().equals(StoragePoolStatus.Maintenance))) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public List<StoragePoolVO> ListByDataCenterHypervisor(long datacenterId, HypervisorType type) {
|
|
List<StoragePoolVO> pools = _storagePoolDao.listByDataCenterId(datacenterId);
|
|
List<StoragePoolVO> retPools = new ArrayList<StoragePoolVO>();
|
|
for (StoragePoolVO pool : pools) {
|
|
if (pool.getStatus() != StoragePoolStatus.Up) {
|
|
continue;
|
|
}
|
|
ClusterVO cluster = _clusterDao.findById(pool.getClusterId());
|
|
if (type == cluster.getHypervisorType()) {
|
|
retPools.add(pool);
|
|
}
|
|
}
|
|
Collections.shuffle(retPools);
|
|
return retPools;
|
|
}
|
|
|
|
@Override
|
|
public boolean isLocalStorageActiveOnHost(Long hostId) {
|
|
List<StoragePoolHostVO> storagePoolHostRefs = _storagePoolHostDao.listByHostId(hostId);
|
|
for (StoragePoolHostVO storagePoolHostRef : storagePoolHostRefs) {
|
|
StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolHostRef.getPoolId());
|
|
if (storagePool.getPoolType() == StoragePoolType.LVM || storagePool.getPoolType() == StoragePoolType.EXT) {
|
|
SearchBuilder<VolumeVO> volumeSB = _volsDao.createSearchBuilder();
|
|
volumeSB.and("poolId", volumeSB.entity().getPoolId(), SearchCriteria.Op.EQ);
|
|
volumeSB.and("removed", volumeSB.entity().getRemoved(), SearchCriteria.Op.NULL);
|
|
|
|
SearchBuilder<VMInstanceVO> activeVmSB = _vmInstanceDao.createSearchBuilder();
|
|
activeVmSB.and("state", activeVmSB.entity().getState(), SearchCriteria.Op.IN);
|
|
volumeSB.join("activeVmSB", activeVmSB, volumeSB.entity().getInstanceId(), activeVmSB.entity().getId(), JoinBuilder.JoinType.INNER);
|
|
|
|
SearchCriteria<VolumeVO> volumeSC = volumeSB.create();
|
|
volumeSC.setParameters("poolId", storagePool.getId());
|
|
volumeSC.setJoinParameters("activeVmSB", "state", State.Starting, State.Running, State.Stopping, State.Migrating);
|
|
|
|
List<VolumeVO> volumes = _volsDao.search(volumeSC, null);
|
|
if (volumes.size() > 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected StoragePoolVO findStoragePool(DiskProfile dskCh, final DataCenterVO dc, HostPodVO pod, Long clusterId, Long hostId, VMInstanceVO vm, final Set<StoragePool> avoid) {
|
|
|
|
VirtualMachineProfile<VMInstanceVO> profile = new VirtualMachineProfileImpl<VMInstanceVO>(vm);
|
|
for (StoragePoolAllocator allocator : _storagePoolAllocators) {
|
|
final List<StoragePool> poolList = allocator.allocateToPool(dskCh, profile, dc.getId(), pod.getId(), clusterId, hostId, avoid, 1);
|
|
if (poolList != null && !poolList.isEmpty()) {
|
|
return (StoragePoolVO) poolList.get(0);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Answer[] sendToPool(StoragePool pool, Commands cmds) throws StorageUnavailableException {
|
|
return sendToPool(pool, null, null, cmds).second();
|
|
}
|
|
|
|
@Override
|
|
public Answer sendToPool(StoragePool pool, long[] hostIdsToTryFirst, Command cmd) throws StorageUnavailableException {
|
|
Answer[] answers = sendToPool(pool, hostIdsToTryFirst, null, new Commands(cmd)).second();
|
|
if (answers == null) {
|
|
return null;
|
|
}
|
|
return answers[0];
|
|
}
|
|
|
|
@Override
|
|
public Answer sendToPool(StoragePool pool, Command cmd) throws StorageUnavailableException {
|
|
Answer[] answers = sendToPool(pool, new Commands(cmd));
|
|
if (answers == null) {
|
|
return null;
|
|
}
|
|
return answers[0];
|
|
}
|
|
|
|
@Override
|
|
public Answer sendToPool(long poolId, Command cmd) throws StorageUnavailableException {
|
|
StoragePool pool = _storagePoolDao.findById(poolId);
|
|
return sendToPool(pool, cmd);
|
|
}
|
|
|
|
@Override
|
|
public Answer[] sendToPool(long poolId, Commands cmds) throws StorageUnavailableException {
|
|
StoragePool pool = _storagePoolDao.findById(poolId);
|
|
return sendToPool(pool, cmds);
|
|
}
|
|
|
|
protected DiskProfile createDiskCharacteristics(VolumeVO volume, VMTemplateVO template, DataCenterVO dc, DiskOfferingVO diskOffering) {
|
|
if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) {
|
|
SearchCriteria<VMTemplateHostVO> sc = HostTemplateStatesSearch.create();
|
|
sc.setParameters("id", template.getId());
|
|
sc.setParameters("state", com.cloud.storage.VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
|
|
sc.setJoinParameters("host", "dcId", dc.getId());
|
|
|
|
List<VMTemplateHostVO> sss = _vmTemplateHostDao.search(sc, null);
|
|
if (sss.size() == 0) {
|
|
throw new CloudRuntimeException("Template " + template.getName() + " has not been completely downloaded to zone " + dc.getId());
|
|
}
|
|
VMTemplateHostVO ss = sss.get(0);
|
|
|
|
return new DiskProfile(volume.getId(), volume.getVolumeType(), volume.getName(), diskOffering.getId(), ss.getSize(), diskOffering.getTagsArray(), diskOffering.getUseLocalStorage(),
|
|
diskOffering.isRecreatable(), Storage.ImageFormat.ISO != template.getFormat() ? template.getId() : null);
|
|
} else {
|
|
return new DiskProfile(volume.getId(), volume.getVolumeType(), volume.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(),
|
|
diskOffering.getUseLocalStorage(), diskOffering.isRecreatable(), null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canVmRestartOnAnotherServer(long vmId) {
|
|
List<VolumeVO> vols = _volsDao.findCreatedByInstance(vmId);
|
|
for (VolumeVO vol : vols) {
|
|
if (!vol.isRecreatable() && !vol.getPoolType().isShared()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@DB
|
|
protected Pair<VolumeVO, String> createVolumeFromSnapshot(VolumeVO volume, SnapshotVO snapshot) {
|
|
VolumeVO createdVolume = null;
|
|
Long volumeId = volume.getId();
|
|
|
|
String volumeFolder = null;
|
|
|
|
try {
|
|
stateTransitTo(volume, Volume.Event.CreateRequested);
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug(e.toString());
|
|
return null;
|
|
}
|
|
// Create the Volume object and save it so that we can return it to the user
|
|
Account account = _accountDao.findById(volume.getAccountId());
|
|
|
|
final HashSet<StoragePool> poolsToAvoid = new HashSet<StoragePool>();
|
|
StoragePoolVO pool = null;
|
|
boolean success = false;
|
|
Set<Long> podsToAvoid = new HashSet<Long>();
|
|
Pair<HostPodVO, Long> pod = null;
|
|
String volumeUUID = null;
|
|
String details = null;
|
|
|
|
DiskOfferingVO diskOffering = _diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId());
|
|
DataCenterVO dc = _dcDao.findById(volume.getDataCenterId());
|
|
DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType());
|
|
|
|
int retry = 0;
|
|
// Determine what pod to store the volume in
|
|
while ((pod = _resourceMgr.findPod(null, null, dc, account.getId(), podsToAvoid)) != null) {
|
|
podsToAvoid.add(pod.first().getId());
|
|
// Determine what storage pool to store the volume in
|
|
while ((pool = findStoragePool(dskCh, dc, pod.first(), null, null, null, poolsToAvoid)) != null) {
|
|
poolsToAvoid.add(pool);
|
|
volumeFolder = pool.getPath();
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Attempting to create volume from snapshotId: " + snapshot.getId() + " on storage pool " + pool.getName());
|
|
}
|
|
|
|
// Get the newly created VDI from the snapshot.
|
|
// This will return a null volumePath if it could not be created
|
|
Pair<String, String> volumeDetails = createVDIFromSnapshot(UserContext.current().getCallerUserId(), snapshot, pool);
|
|
|
|
volumeUUID = volumeDetails.first();
|
|
details = volumeDetails.second();
|
|
|
|
if (volumeUUID != null) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Volume with UUID " + volumeUUID + " was created on storage pool " + pool.getName());
|
|
}
|
|
success = true;
|
|
break; // break out of the "find storage pool" loop
|
|
} else {
|
|
retry++;
|
|
if (retry >= 3) {
|
|
_volsDao.expunge(volumeId);
|
|
String msg = "Unable to create volume from snapshot " + snapshot.getId() + " after retrying 3 times, due to " + details;
|
|
s_logger.debug(msg);
|
|
throw new CloudRuntimeException(msg);
|
|
|
|
}
|
|
}
|
|
s_logger.warn("Unable to create volume on pool " + pool.getName() + ", reason: " + details);
|
|
}
|
|
|
|
if (success) {
|
|
break; // break out of the "find pod" loop
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
_volsDao.expunge(volumeId);
|
|
String msg = "Unable to create volume from snapshot " + snapshot.getId() + " due to " + details;
|
|
s_logger.debug(msg);
|
|
throw new CloudRuntimeException(msg);
|
|
|
|
}
|
|
|
|
createdVolume = _volsDao.findById(volumeId);
|
|
|
|
try {
|
|
if (success) {
|
|
createdVolume.setPodId(pod.first().getId());
|
|
createdVolume.setPoolId(pool.getId());
|
|
createdVolume.setPoolType(pool.getPoolType());
|
|
createdVolume.setFolder(volumeFolder);
|
|
createdVolume.setPath(volumeUUID);
|
|
createdVolume.setDomainId(account.getDomainId());
|
|
stateTransitTo(createdVolume, Volume.Event.OperationSucceeded);
|
|
}
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Failed to update volume state: " + e.toString());
|
|
return null;
|
|
}
|
|
|
|
return new Pair<VolumeVO, String>(createdVolume, details);
|
|
}
|
|
|
|
@Override
|
|
public boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException {
|
|
return _volStateMachine.transitTo(vol, event, null, _volsDao);
|
|
}
|
|
|
|
protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId) {
|
|
|
|
// By default, assume failure.
|
|
VolumeVO createdVolume = null;
|
|
SnapshotVO snapshot = _snapshotDao.findById(snapshotId); // Precondition: snapshot is not null and not removed.
|
|
|
|
Pair<VolumeVO, String> volumeDetails = createVolumeFromSnapshot(volume, snapshot);
|
|
if (volumeDetails != null) {
|
|
createdVolume = volumeDetails.first();
|
|
UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, createdVolume.getAccountId(), createdVolume.getDataCenterId(), createdVolume.getId(), createdVolume.getName(),
|
|
createdVolume.getDiskOfferingId(), null, createdVolume.getSize());
|
|
_usageEventDao.persist(usageEvent);
|
|
}
|
|
return createdVolume;
|
|
}
|
|
|
|
protected Pair<String, String> createVDIFromSnapshot(long userId, SnapshotVO snapshot, StoragePoolVO pool) {
|
|
String vdiUUID = null;
|
|
Long snapshotId = snapshot.getId();
|
|
Long volumeId = snapshot.getVolumeId();
|
|
Long dcId = snapshot.getDataCenterId();
|
|
String secondaryStoragePoolUrl = _snapMgr.getSecondaryStorageURL(snapshot);
|
|
long accountId = snapshot.getAccountId();
|
|
|
|
String backedUpSnapshotUuid = snapshot.getBackupSnapshotId();
|
|
snapshot = _snapshotDao.findById(snapshotId);
|
|
if (snapshot.getVersion().trim().equals("2.1")) {
|
|
VolumeVO volume = _volsDao.findByIdIncludingRemoved(volumeId);
|
|
if (volume == null) {
|
|
throw new CloudRuntimeException("failed to upgrade snapshot " + snapshotId + " due to unable to find orignal volume:" + volumeId + ", try it later ");
|
|
}
|
|
if (volume.getTemplateId() == null) {
|
|
_snapshotDao.updateSnapshotVersion(volumeId, "2.1", "2.2");
|
|
} else {
|
|
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(volume.getTemplateId());
|
|
if (template == null) {
|
|
throw new CloudRuntimeException("failed to upgrade snapshot " + snapshotId + " due to unalbe to find orignal template :" + volume.getTemplateId() + ", try it later ");
|
|
}
|
|
Long templateId = template.getId();
|
|
Long tmpltAccountId = template.getAccountId();
|
|
if (!_snapshotDao.lockInLockTable(snapshotId.toString(), 10)) {
|
|
throw new CloudRuntimeException("failed to upgrade snapshot " + snapshotId + " due to this snapshot is being used, try it later ");
|
|
}
|
|
UpgradeSnapshotCommand cmd = new UpgradeSnapshotCommand(null, secondaryStoragePoolUrl, dcId, accountId, volumeId, templateId, tmpltAccountId, null, snapshot.getBackupSnapshotId(),
|
|
snapshot.getName(), "2.1");
|
|
Answer answer = null;
|
|
try {
|
|
answer = sendToPool(pool, cmd);
|
|
} catch (StorageUnavailableException e) {
|
|
} finally {
|
|
_snapshotDao.unlockFromLockTable(snapshotId.toString());
|
|
}
|
|
if ((answer != null) && answer.getResult()) {
|
|
_snapshotDao.updateSnapshotVersion(volumeId, "2.1", "2.2");
|
|
} else {
|
|
return new Pair<String, String>(null, "Unable to upgrade snapshot from 2.1 to 2.2 for " + snapshot.getId());
|
|
}
|
|
}
|
|
}
|
|
String basicErrMsg = "Failed to create volume from " + snapshot.getName() + " on pool " + pool;
|
|
try {
|
|
if (snapshot.getSwiftId() != null && snapshot.getSwiftId() != 0) {
|
|
_snapshotMgr.downloadSnapshotsFromSwift(snapshot);
|
|
} else if (snapshot.getS3Id() != null && snapshot.getS3Id() != 0) {
|
|
_snapshotMgr.downloadSnapshotsFromS3(snapshot);
|
|
}
|
|
CreateVolumeFromSnapshotCommand createVolumeFromSnapshotCommand = new CreateVolumeFromSnapshotCommand(pool, secondaryStoragePoolUrl, dcId, accountId, volumeId,
|
|
backedUpSnapshotUuid, snapshot.getName(), _createVolumeFromSnapshotWait);
|
|
CreateVolumeFromSnapshotAnswer answer;
|
|
if (!_snapshotDao.lockInLockTable(snapshotId.toString(), 10)) {
|
|
throw new CloudRuntimeException("failed to create volume from " + snapshotId + " due to this snapshot is being used, try it later ");
|
|
}
|
|
answer = (CreateVolumeFromSnapshotAnswer) sendToPool(pool, createVolumeFromSnapshotCommand);
|
|
if (answer != null && answer.getResult()) {
|
|
vdiUUID = answer.getVdi();
|
|
} else {
|
|
s_logger.error(basicErrMsg + " due to " + ((answer == null) ? "null" : answer.getDetails()));
|
|
throw new CloudRuntimeException(basicErrMsg);
|
|
}
|
|
} catch (StorageUnavailableException e) {
|
|
s_logger.error(basicErrMsg);
|
|
} finally {
|
|
if (snapshot.getSwiftId() != null) {
|
|
_snapshotMgr.deleteSnapshotsDirForVolume(secondaryStoragePoolUrl, dcId, accountId, volumeId);
|
|
}
|
|
_snapshotDao.unlockFromLockTable(snapshotId.toString());
|
|
}
|
|
return new Pair<String, String>(vdiUUID, basicErrMsg);
|
|
}
|
|
|
|
|
|
@Override
|
|
@DB
|
|
public VolumeVO copyVolumeFromSecToPrimary(VolumeVO volume, VMInstanceVO vm, VMTemplateVO template, DataCenterVO dc, HostPodVO pod, Long clusterId, ServiceOfferingVO offering, DiskOfferingVO diskOffering,
|
|
List<StoragePoolVO> avoids, long size, HypervisorType hyperType) throws NoTransitionException {
|
|
|
|
final HashSet<StoragePool> avoidPools = new HashSet<StoragePool>(avoids);
|
|
DiskProfile dskCh = createDiskCharacteristics(volume, template, dc, diskOffering);
|
|
dskCh.setHyperType(vm.getHypervisorType());
|
|
// Find a suitable storage to create volume on
|
|
StoragePoolVO destPool = findStoragePool(dskCh, dc, pod, clusterId, null, vm, avoidPools);
|
|
|
|
// Copy the volume from secondary storage to the destination storage pool
|
|
stateTransitTo(volume, Event.CopyRequested);
|
|
VolumeHostVO volumeHostVO = _volumeHostDao.findByVolumeId(volume.getId());
|
|
HostVO secStorage = _hostDao.findById(volumeHostVO.getHostId());
|
|
String secondaryStorageURL = secStorage.getStorageUrl();
|
|
String[] volumePath = volumeHostVO.getInstallPath().split("/");
|
|
String volumeUUID = volumePath[volumePath.length - 1].split("\\.")[0];
|
|
|
|
CopyVolumeCommand cvCmd = new CopyVolumeCommand(volume.getId(), volumeUUID, destPool, secondaryStorageURL, false, _copyvolumewait);
|
|
CopyVolumeAnswer cvAnswer;
|
|
try {
|
|
cvAnswer = (CopyVolumeAnswer) sendToPool(destPool, cvCmd);
|
|
} catch (StorageUnavailableException e1) {
|
|
stateTransitTo(volume, Event.CopyFailed);
|
|
throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool.");
|
|
}
|
|
|
|
if (cvAnswer == null || !cvAnswer.getResult()) {
|
|
stateTransitTo(volume, Event.CopyFailed);
|
|
throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool.");
|
|
}
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
volume.setPath(cvAnswer.getVolumePath());
|
|
volume.setFolder(destPool.getPath());
|
|
volume.setPodId(destPool.getPodId());
|
|
volume.setPoolId(destPool.getId());
|
|
volume.setPodId(destPool.getPodId());
|
|
stateTransitTo(volume, Event.CopySucceeded);
|
|
UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), volume.getDiskOfferingId(), null, volume.getSize());
|
|
_usageEventDao.persist(usageEvent);
|
|
_volumeHostDao.remove(volumeHostVO.getId());
|
|
txn.commit();
|
|
return volume;
|
|
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public VolumeVO createVolume(VolumeVO volume, VMInstanceVO vm, VMTemplateVO template, DataCenterVO dc, HostPodVO pod, Long clusterId, ServiceOfferingVO offering, DiskOfferingVO diskOffering,
|
|
List<StoragePoolVO> avoids, long size, HypervisorType hyperType) {
|
|
StoragePoolVO pool = null;
|
|
final HashSet<StoragePool> avoidPools = new HashSet<StoragePool>(avoids);
|
|
|
|
try {
|
|
stateTransitTo(volume, Volume.Event.CreateRequested);
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Unable to update volume state: " + e.toString());
|
|
return null;
|
|
}
|
|
|
|
if (diskOffering != null && diskOffering.isCustomized()) {
|
|
diskOffering.setDiskSize(size);
|
|
}
|
|
DiskProfile dskCh = null;
|
|
if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) {
|
|
dskCh = createDiskCharacteristics(volume, template, dc, offering);
|
|
} else {
|
|
dskCh = createDiskCharacteristics(volume, template, dc, diskOffering);
|
|
}
|
|
|
|
dskCh.setHyperType(hyperType);
|
|
|
|
VolumeTO created = null;
|
|
int retry = _retry;
|
|
while (--retry >= 0) {
|
|
created = null;
|
|
|
|
long podId = pod.getId();
|
|
pod = _podDao.findById(podId);
|
|
if (pod == null) {
|
|
s_logger.warn("Unable to find pod " + podId + " when create volume " + volume.getName());
|
|
break;
|
|
}
|
|
|
|
pool = findStoragePool(dskCh, dc, pod, clusterId, vm.getHostId(), vm, avoidPools);
|
|
if (pool == null) {
|
|
s_logger.warn("Unable to find storage poll when create volume " + volume.getName());
|
|
break;
|
|
}
|
|
|
|
avoidPools.add(pool);
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Trying to create " + volume + " on " + pool);
|
|
}
|
|
|
|
CreateCommand cmd = null;
|
|
VMTemplateStoragePoolVO tmpltStoredOn = null;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) {
|
|
if (pool.getPoolType() == StoragePoolType.CLVM) {
|
|
//prepareISOForCreate does what we need, which is to tell us where the template is
|
|
VMTemplateHostVO tmpltHostOn = _tmpltMgr.prepareISOForCreate(template, pool);
|
|
if (tmpltHostOn == null) {
|
|
continue;
|
|
}
|
|
HostVO secondaryStorageHost = _hostDao.findById(tmpltHostOn.getHostId());
|
|
String tmpltHostUrl = secondaryStorageHost.getStorageUrl();
|
|
String fullTmpltUrl = tmpltHostUrl + "/" + tmpltHostOn.getInstallPath();
|
|
cmd = new CreateCommand(dskCh, fullTmpltUrl, new StorageFilerTO(pool));
|
|
} else {
|
|
tmpltStoredOn = _tmpltMgr.prepareTemplateForCreate(template, pool);
|
|
if (tmpltStoredOn == null) {
|
|
continue;
|
|
}
|
|
cmd = new CreateCommand(dskCh, tmpltStoredOn.getLocalDownloadPath(), new StorageFilerTO(pool));
|
|
}
|
|
} else {
|
|
if (volume.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO == template.getFormat()) {
|
|
VMTemplateHostVO tmpltHostOn = _tmpltMgr.prepareISOForCreate(template, pool);
|
|
if (tmpltHostOn == null) {
|
|
throw new CloudRuntimeException("Did not find ISO in secondry storage in zone " + pool.getDataCenterId());
|
|
}
|
|
}
|
|
cmd = new CreateCommand(dskCh, new StorageFilerTO(pool));
|
|
}
|
|
|
|
try {
|
|
Answer answer = sendToPool(pool, cmd);
|
|
if (answer != null && answer.getResult()) {
|
|
created = ((CreateAnswer) answer).getVolume();
|
|
break;
|
|
}
|
|
|
|
if (tmpltStoredOn != null && answer != null && (answer instanceof CreateAnswer) && ((CreateAnswer) answer).templateReloadRequested()) {
|
|
if (!_tmpltMgr.resetTemplateDownloadStateOnPool(tmpltStoredOn.getId())) {
|
|
break; // break out of template-redeploy retry loop
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} catch (StorageUnavailableException e) {
|
|
s_logger.debug("Storage unavailable for " + pool.getId());
|
|
break; // break out of template-redeploy retry loop
|
|
}
|
|
}
|
|
|
|
if (created != null) {
|
|
break;
|
|
}
|
|
|
|
s_logger.debug("Retrying the create because it failed on pool " + pool);
|
|
}
|
|
|
|
if (created == null) {
|
|
return null;
|
|
} else {
|
|
volume.setFolder(pool.getPath());
|
|
volume.setPath(created.getPath());
|
|
volume.setSize(created.getSize());
|
|
volume.setPoolType(pool.getPoolType());
|
|
volume.setPoolId(pool.getId());
|
|
volume.setPodId(pod.getId());
|
|
try {
|
|
stateTransitTo(volume, Volume.Event.OperationSucceeded);
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Unable to update volume state: " + e.toString());
|
|
return null;
|
|
}
|
|
return volume;
|
|
}
|
|
}
|
|
|
|
public Long chooseHostForStoragePool(StoragePoolVO poolVO, List<Long> avoidHosts, boolean sendToVmResidesOn, Long vmId) {
|
|
if (sendToVmResidesOn) {
|
|
if (vmId != null) {
|
|
VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId);
|
|
if (vmInstance != null) {
|
|
Long hostId = vmInstance.getHostId();
|
|
if (hostId != null && !avoidHosts.contains(vmInstance.getHostId())) {
|
|
return hostId;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Can't find the vm where host resides on(vm is destroyed? or volume is detached from vm), randomly choose
|
|
* a host
|
|
* to send the cmd
|
|
*/
|
|
}
|
|
List<StoragePoolHostVO> poolHosts = _poolHostDao.listByHostStatus(poolVO.getId(), Status.Up);
|
|
Collections.shuffle(poolHosts);
|
|
if (poolHosts != null && poolHosts.size() > 0) {
|
|
for (StoragePoolHostVO sphvo : poolHosts) {
|
|
if (!avoidHosts.contains(sphvo.getHostId())) {
|
|
return sphvo.getHostId();
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
|
_name = name;
|
|
|
|
Map<String, String> configs = _configDao.getConfiguration("management-server", params);
|
|
|
|
String overProvisioningFactorStr = configs.get("storage.overprovisioning.factor");
|
|
if (overProvisioningFactorStr != null) {
|
|
_overProvisioningFactor = new BigDecimal(overProvisioningFactorStr);
|
|
}
|
|
|
|
_retry = NumbersUtil.parseInt(configs.get(Config.StartRetry.key()), 10);
|
|
_pingInterval = NumbersUtil.parseInt(configs.get("ping.interval"), 60);
|
|
_hostRetry = NumbersUtil.parseInt(configs.get("host.retry"), 2);
|
|
_storagePoolAcquisitionWaitSeconds = NumbersUtil.parseInt(configs.get("pool.acquisition.wait.seconds"), 1800);
|
|
s_logger.info("pool.acquisition.wait.seconds is configured as " + _storagePoolAcquisitionWaitSeconds + " seconds");
|
|
|
|
_agentMgr.registerForHostEvents(new StoragePoolMonitor(this, _storagePoolDao), true, false, true);
|
|
|
|
String storageCleanupEnabled = configs.get("storage.cleanup.enabled");
|
|
_storageCleanupEnabled = (storageCleanupEnabled == null) ? true : Boolean.parseBoolean(storageCleanupEnabled);
|
|
|
|
String value = _configDao.getValue(Config.CreateVolumeFromSnapshotWait.toString());
|
|
_createVolumeFromSnapshotWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CreateVolumeFromSnapshotWait.getDefaultValue()));
|
|
|
|
value = _configDao.getValue(Config.CopyVolumeWait.toString());
|
|
_copyvolumewait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CopyVolumeWait.getDefaultValue()));
|
|
|
|
value = _configDao.getValue(Config.RecreateSystemVmEnabled.key());
|
|
_recreateSystemVmEnabled = Boolean.parseBoolean(value);
|
|
|
|
value = _configDao.getValue(Config.StorageTemplateCleanupEnabled.key());
|
|
_templateCleanupEnabled = (value == null ? true : Boolean.parseBoolean(value));
|
|
|
|
String time = configs.get("storage.cleanup.interval");
|
|
_storageCleanupInterval = NumbersUtil.parseInt(time, 86400);
|
|
|
|
String storageUsedThreshold = _configDao.getValue(Config.StorageCapacityDisableThreshold.key());
|
|
if (storageUsedThreshold != null) {
|
|
_storageUsedThreshold = Double.parseDouble(storageUsedThreshold);
|
|
}
|
|
|
|
String storageAllocatedThreshold = _configDao.getValue(Config.StorageAllocatedCapacityDisableThreshold.key());
|
|
if (storageAllocatedThreshold != null) {
|
|
_storageAllocatedThreshold = Double.parseDouble(storageAllocatedThreshold);
|
|
}
|
|
|
|
String globalStorageOverprovisioningFactor = configs.get("storage.overprovisioning.factor");
|
|
_storageOverprovisioningFactor = new BigDecimal(NumbersUtil.parseFloat(globalStorageOverprovisioningFactor, 2.0f));
|
|
|
|
s_logger.info("Storage cleanup enabled: " + _storageCleanupEnabled + ", interval: " + _storageCleanupInterval + ", template cleanup enabled: " + _templateCleanupEnabled);
|
|
|
|
String workers = configs.get("expunge.workers");
|
|
int wrks = NumbersUtil.parseInt(workers, 10);
|
|
_executor = Executors.newScheduledThreadPool(wrks, new NamedThreadFactory("StorageManager-Scavenger"));
|
|
|
|
_agentMgr.registerForHostEvents(ComponentLocator.inject(LocalStoragePoolListener.class), true, false, false);
|
|
|
|
String maxVolumeSizeInGbString = _configDao.getValue("storage.max.volume.size");
|
|
_maxVolumeSizeInGb = NumbersUtil.parseLong(maxVolumeSizeInGbString, 2000);
|
|
|
|
String _customDiskOfferingMinSizeStr = _configDao.getValue(Config.CustomDiskOfferingMinSize.toString());
|
|
_customDiskOfferingMinSize = NumbersUtil.parseInt(_customDiskOfferingMinSizeStr, Integer.parseInt(Config.CustomDiskOfferingMinSize.getDefaultValue()));
|
|
|
|
String _customDiskOfferingMaxSizeStr = _configDao.getValue(Config.CustomDiskOfferingMaxSize.toString());
|
|
_customDiskOfferingMaxSize = NumbersUtil.parseInt(_customDiskOfferingMaxSizeStr, Integer.parseInt(Config.CustomDiskOfferingMaxSize.getDefaultValue()));
|
|
|
|
HostTemplateStatesSearch = _vmTemplateHostDao.createSearchBuilder();
|
|
HostTemplateStatesSearch.and("id", HostTemplateStatesSearch.entity().getTemplateId(), SearchCriteria.Op.EQ);
|
|
HostTemplateStatesSearch.and("state", HostTemplateStatesSearch.entity().getDownloadState(), SearchCriteria.Op.EQ);
|
|
|
|
SearchBuilder<HostVO> HostSearch = _hostDao.createSearchBuilder();
|
|
HostSearch.and("dcId", HostSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ);
|
|
|
|
HostTemplateStatesSearch.join("host", HostSearch, HostSearch.entity().getId(), HostTemplateStatesSearch.entity().getHostId(), JoinBuilder.JoinType.INNER);
|
|
HostSearch.done();
|
|
HostTemplateStatesSearch.done();
|
|
|
|
_serverId = _msServer.getId();
|
|
|
|
UpHostsInPoolSearch = _storagePoolHostDao.createSearchBuilder(Long.class);
|
|
UpHostsInPoolSearch.selectField(UpHostsInPoolSearch.entity().getHostId());
|
|
SearchBuilder<HostVO> hostSearch = _hostDao.createSearchBuilder();
|
|
hostSearch.and("status", hostSearch.entity().getStatus(), Op.EQ);
|
|
hostSearch.and("resourceState", hostSearch.entity().getResourceState(), Op.EQ);
|
|
UpHostsInPoolSearch.join("hosts", hostSearch, hostSearch.entity().getId(), UpHostsInPoolSearch.entity().getHostId(), JoinType.INNER);
|
|
UpHostsInPoolSearch.and("pool", UpHostsInPoolSearch.entity().getPoolId(), Op.EQ);
|
|
UpHostsInPoolSearch.done();
|
|
|
|
StoragePoolSearch = _vmInstanceDao.createSearchBuilder();
|
|
|
|
SearchBuilder<VolumeVO> volumeSearch = _volumeDao.createSearchBuilder();
|
|
volumeSearch.and("volumeType", volumeSearch.entity().getVolumeType(), SearchCriteria.Op.EQ);
|
|
volumeSearch.and("poolId", volumeSearch.entity().getPoolId(), SearchCriteria.Op.EQ);
|
|
StoragePoolSearch.join("vmVolume", volumeSearch, volumeSearch.entity().getInstanceId(), StoragePoolSearch.entity().getId(), JoinBuilder.JoinType.INNER);
|
|
StoragePoolSearch.done();
|
|
|
|
LocalStorageSearch = _storagePoolDao.createSearchBuilder();
|
|
SearchBuilder<StoragePoolHostVO> storageHostSearch = _storagePoolHostDao.createSearchBuilder();
|
|
storageHostSearch.and("hostId", storageHostSearch.entity().getHostId(), SearchCriteria.Op.EQ);
|
|
LocalStorageSearch.join("poolHost", storageHostSearch, storageHostSearch.entity().getPoolId(), LocalStorageSearch.entity().getId(), JoinBuilder.JoinType.INNER);
|
|
LocalStorageSearch.and("type", LocalStorageSearch.entity().getPoolType(), SearchCriteria.Op.IN);
|
|
LocalStorageSearch.done();
|
|
return true;
|
|
}
|
|
|
|
public String getRandomVolumeName() {
|
|
return UUID.randomUUID().toString();
|
|
}
|
|
|
|
@Override
|
|
public boolean volumeOnSharedStoragePool(VolumeVO volume) {
|
|
Long poolId = volume.getPoolId();
|
|
if (poolId == null) {
|
|
return false;
|
|
} else {
|
|
StoragePoolVO pool = _storagePoolDao.findById(poolId);
|
|
|
|
if (pool == null) {
|
|
return false;
|
|
} else {
|
|
return pool.isShared();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean volumeInactive(VolumeVO volume) {
|
|
Long vmId = volume.getInstanceId();
|
|
if (vmId != null) {
|
|
UserVm vm = _userVmDao.findById(vmId);
|
|
if (vm == null) {
|
|
return true;
|
|
}
|
|
State state = vm.getState();
|
|
if (state.equals(State.Stopped) || state.equals(State.Destroyed)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public String getVmNameOnVolume(VolumeVO volume) {
|
|
Long vmId = volume.getInstanceId();
|
|
if (vmId != null) {
|
|
VMInstanceVO vm = _vmInstanceDao.findById(vmId);
|
|
|
|
if (vm == null) {
|
|
return null;
|
|
}
|
|
return vm.getInstanceName();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Pair<String, String> getAbsoluteIsoPath(long templateId, long dataCenterId) {
|
|
String isoPath = null;
|
|
|
|
List<HostVO> storageHosts = _resourceMgr.listAllHostsInOneZoneByType(Host.Type.SecondaryStorage, dataCenterId);
|
|
if (storageHosts != null) {
|
|
for (HostVO storageHost : storageHosts) {
|
|
List<VMTemplateHostVO> templateHostVOs = _vmTemplateHostDao.listByTemplateHostStatus(templateId, storageHost.getId(), VMTemplateStorageResourceAssoc.Status.DOWNLOADED );
|
|
if (templateHostVOs != null && !templateHostVOs.isEmpty()) {
|
|
VMTemplateHostVO tmpHostVO = templateHostVOs.get(0);
|
|
isoPath = storageHost.getStorageUrl() + "/" + tmpHostVO.getInstallPath();
|
|
return new Pair<String, String>(isoPath, storageHost.getStorageUrl());
|
|
}
|
|
}
|
|
}
|
|
s_logger.warn("Unable to find secondary storage in zone id=" + dataCenterId);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public String getSecondaryStorageURL(long zoneId) {
|
|
// Determine the secondary storage URL
|
|
HostVO secondaryStorageHost = getSecondaryStorageHost(zoneId);
|
|
|
|
if (secondaryStorageHost == null) {
|
|
return null;
|
|
}
|
|
|
|
return secondaryStorageHost.getStorageUrl();
|
|
}
|
|
|
|
@Override
|
|
public HostVO getSecondaryStorageHost(long zoneId, long tmpltId) {
|
|
List<HostVO> hosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(zoneId);
|
|
if (hosts == null || hosts.size() == 0) {
|
|
return null;
|
|
}
|
|
for (HostVO host : hosts) {
|
|
VMTemplateHostVO tmpltHost = _vmTemplateHostDao.findByHostTemplate(host.getId(), tmpltId);
|
|
if (tmpltHost != null && !tmpltHost.getDestroyed() && tmpltHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
|
|
return host;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public VMTemplateHostVO getTemplateHostRef(long zoneId, long tmpltId, boolean readyOnly) {
|
|
List<HostVO> hosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(zoneId);
|
|
if (hosts == null || hosts.size() == 0) {
|
|
return null;
|
|
}
|
|
VMTemplateHostVO inProgress = null;
|
|
VMTemplateHostVO other = null;
|
|
for (HostVO host : hosts) {
|
|
VMTemplateHostVO tmpltHost = _vmTemplateHostDao.findByHostTemplate(host.getId(), tmpltId);
|
|
if (tmpltHost != null && !tmpltHost.getDestroyed()) {
|
|
if (tmpltHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
|
|
return tmpltHost;
|
|
} else if (tmpltHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) {
|
|
inProgress = tmpltHost;
|
|
} else {
|
|
other = tmpltHost;
|
|
}
|
|
}
|
|
}
|
|
if (inProgress != null) {
|
|
return inProgress;
|
|
}
|
|
return other;
|
|
}
|
|
|
|
@Override
|
|
public HostVO getSecondaryStorageHost(long zoneId) {
|
|
List<HostVO> hosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(zoneId);
|
|
if (hosts == null || hosts.size() == 0) {
|
|
hosts = _ssvmMgr.listLocalSecondaryStorageHostsInOneZone(zoneId);
|
|
if (hosts.isEmpty()) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
int size = hosts.size();
|
|
Random rn = new Random();
|
|
int index = rn.nextInt(size);
|
|
return hosts.get(index);
|
|
}
|
|
|
|
@Override
|
|
public List<HostVO> getSecondaryStorageHosts(long zoneId) {
|
|
List<HostVO> hosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(zoneId);
|
|
if (hosts == null || hosts.size() == 0) {
|
|
hosts = _ssvmMgr.listLocalSecondaryStorageHostsInOneZone(zoneId);
|
|
if (hosts.isEmpty()) {
|
|
return new ArrayList<HostVO>();
|
|
}
|
|
}
|
|
return hosts;
|
|
}
|
|
|
|
@Override
|
|
public String getStoragePoolTags(long poolId) {
|
|
return _configMgr.listToCsvTags(_storagePoolDao.searchForStoragePoolDetails(poolId, "true"));
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return _name;
|
|
}
|
|
|
|
@Override
|
|
public boolean start() {
|
|
if (_storageCleanupEnabled) {
|
|
Random generator = new Random();
|
|
int initialDelay = generator.nextInt(_storageCleanupInterval);
|
|
_executor.scheduleWithFixedDelay(new StorageGarbageCollector(), initialDelay, _storageCleanupInterval, TimeUnit.SECONDS);
|
|
} else {
|
|
s_logger.debug("Storage cleanup is not enabled, so the storage cleanup thread is not being scheduled.");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean stop() {
|
|
if (_storageCleanupEnabled) {
|
|
_executor.shutdown();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected StorageManagerImpl() {
|
|
_volStateMachine = Volume.State.getStateMachine();
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("rawtypes")
|
|
public StoragePoolVO createPool(CreateStoragePoolCmd cmd) throws ResourceInUseException, IllegalArgumentException, UnknownHostException, ResourceUnavailableException {
|
|
Long clusterId = cmd.getClusterId();
|
|
Long podId = cmd.getPodId();
|
|
Map ds = cmd.getDetails();
|
|
|
|
if (clusterId != null && podId == null) {
|
|
throw new InvalidParameterValueException("Cluster id requires pod id");
|
|
}
|
|
|
|
Map<String, String> details = new HashMap<String, String>();
|
|
if (ds != null) {
|
|
Collection detailsCollection = ds.values();
|
|
Iterator it = detailsCollection.iterator();
|
|
while (it.hasNext()) {
|
|
HashMap d = (HashMap) it.next();
|
|
Iterator it2 = d.entrySet().iterator();
|
|
while (it2.hasNext()) {
|
|
Map.Entry entry = (Map.Entry) it2.next();
|
|
details.put((String) entry.getKey(), (String) entry.getValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
// verify input parameters
|
|
Long zoneId = cmd.getZoneId();
|
|
DataCenterVO zone = _dcDao.findById(cmd.getZoneId());
|
|
if (zone == null) {
|
|
throw new InvalidParameterValueException("unable to find zone by id " + zoneId);
|
|
}
|
|
// Check if zone is disabled
|
|
Account account = UserContext.current().getCaller();
|
|
if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(account.getType())) {
|
|
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
|
|
}
|
|
|
|
// Check if there is host up in this cluster
|
|
List<HostVO> allHosts = _resourceMgr.listAllUpAndEnabledHosts(Host.Type.Routing, clusterId, podId, zoneId);
|
|
if (allHosts.isEmpty()) {
|
|
throw new ResourceUnavailableException("No host up to associate a storage pool with in cluster " + clusterId, Pod.class, podId);
|
|
}
|
|
URI uri = null;
|
|
try {
|
|
uri = new URI(UriUtils.encodeURIComponent(cmd.getUrl()));
|
|
if (uri.getScheme() == null) {
|
|
throw new InvalidParameterValueException("scheme is null " + cmd.getUrl() + ", add nfs:// as a prefix");
|
|
} else if (uri.getScheme().equalsIgnoreCase("nfs")) {
|
|
String uriHost = uri.getHost();
|
|
String uriPath = uri.getPath();
|
|
if (uriHost == null || uriPath == null || uriHost.trim().isEmpty() || uriPath.trim().isEmpty()) {
|
|
throw new InvalidParameterValueException("host or path is null, should be nfs://hostname/path");
|
|
}
|
|
} else if (uri.getScheme().equalsIgnoreCase("sharedMountPoint")) {
|
|
String uriPath = uri.getPath();
|
|
if (uriPath == null) {
|
|
throw new InvalidParameterValueException("host or path is null, should be sharedmountpoint://localhost/path");
|
|
}
|
|
} else if (uri.getScheme().equalsIgnoreCase("rbd")) {
|
|
String uriPath = uri.getPath();
|
|
if (uriPath == null) {
|
|
throw new InvalidParameterValueException("host or path is null, should be rbd://hostname/pool");
|
|
}
|
|
}
|
|
} catch (URISyntaxException e) {
|
|
throw new InvalidParameterValueException(cmd.getUrl() + " is not a valid uri");
|
|
}
|
|
|
|
String tags = cmd.getTags();
|
|
if (tags != null) {
|
|
String[] tokens = tags.split(",");
|
|
|
|
for (String tag : tokens) {
|
|
tag = tag.trim();
|
|
if (tag.length() == 0) {
|
|
continue;
|
|
}
|
|
details.put(tag, "true");
|
|
}
|
|
}
|
|
|
|
String scheme = uri.getScheme();
|
|
String storageHost = uri.getHost();
|
|
String hostPath = uri.getPath();
|
|
String userInfo = uri.getUserInfo();
|
|
int port = uri.getPort();
|
|
StoragePoolVO pool = null;
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("createPool Params @ scheme - " + scheme + " storageHost - " + storageHost + " hostPath - " + hostPath + " port - " + port);
|
|
}
|
|
if (scheme.equalsIgnoreCase("nfs")) {
|
|
if (port == -1) {
|
|
port = 2049;
|
|
}
|
|
pool = new StoragePoolVO(StoragePoolType.NetworkFilesystem, storageHost, port, hostPath);
|
|
if (clusterId == null) {
|
|
throw new IllegalArgumentException("NFS need to have clusters specified for XenServers");
|
|
}
|
|
} else if (scheme.equalsIgnoreCase("file")) {
|
|
if (port == -1) {
|
|
port = 0;
|
|
}
|
|
pool = new StoragePoolVO(StoragePoolType.Filesystem, "localhost", 0, hostPath);
|
|
} else if (scheme.equalsIgnoreCase("sharedMountPoint")) {
|
|
pool = new StoragePoolVO(StoragePoolType.SharedMountPoint, storageHost, 0, hostPath);
|
|
} else if (scheme.equalsIgnoreCase("clvm")) {
|
|
pool = new StoragePoolVO(StoragePoolType.CLVM, storageHost, 0, hostPath.replaceFirst("/", ""));
|
|
} else if (scheme.equalsIgnoreCase("rbd")) {
|
|
if (port == -1) {
|
|
port = 6789;
|
|
}
|
|
pool = new StoragePoolVO(StoragePoolType.RBD, storageHost, port, hostPath.replaceFirst("/", ""), userInfo);
|
|
} else if (scheme.equalsIgnoreCase("PreSetup")) {
|
|
pool = new StoragePoolVO(StoragePoolType.PreSetup, storageHost, 0, hostPath);
|
|
} else if (scheme.equalsIgnoreCase("iscsi")) {
|
|
String[] tokens = hostPath.split("/");
|
|
int lun = NumbersUtil.parseInt(tokens[tokens.length - 1], -1);
|
|
if (port == -1) {
|
|
port = 3260;
|
|
}
|
|
if (lun != -1) {
|
|
if (clusterId == null) {
|
|
throw new IllegalArgumentException("IscsiLUN need to have clusters specified");
|
|
}
|
|
hostPath.replaceFirst("/", "");
|
|
pool = new StoragePoolVO(StoragePoolType.IscsiLUN, storageHost, port, hostPath);
|
|
} else {
|
|
for (StoragePoolDiscoverer discoverer : _discoverers) {
|
|
Map<StoragePoolVO, Map<String, String>> pools;
|
|
try {
|
|
pools = discoverer.find(cmd.getZoneId(), podId, uri, details);
|
|
} catch (DiscoveryException e) {
|
|
throw new IllegalArgumentException("Not enough information for discovery " + uri, e);
|
|
}
|
|
if (pools != null) {
|
|
Map.Entry<StoragePoolVO, Map<String, String>> entry = pools.entrySet().iterator().next();
|
|
pool = entry.getKey();
|
|
details = entry.getValue();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (scheme.equalsIgnoreCase("iso")) {
|
|
if (port == -1) {
|
|
port = 2049;
|
|
}
|
|
pool = new StoragePoolVO(StoragePoolType.ISO, storageHost, port, hostPath);
|
|
} else if (scheme.equalsIgnoreCase("vmfs")) {
|
|
pool = new StoragePoolVO(StoragePoolType.VMFS, "VMFS datastore: " + hostPath, 0, hostPath);
|
|
} else if (scheme.equalsIgnoreCase("ocfs2")) {
|
|
port = 7777;
|
|
pool = new StoragePoolVO(StoragePoolType.OCFS2, "clustered", port, hostPath);
|
|
} else {
|
|
s_logger.warn("Unable to figure out the scheme for URI: " + uri);
|
|
throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + uri);
|
|
}
|
|
|
|
if (pool == null) {
|
|
s_logger.warn("Unable to figure out the scheme for URI: " + uri);
|
|
throw new IllegalArgumentException("Unable to figure out the scheme for URI: " + uri);
|
|
}
|
|
|
|
List<StoragePoolVO> pools = _storagePoolDao.listPoolByHostPath(storageHost, hostPath);
|
|
if (!pools.isEmpty() && !scheme.equalsIgnoreCase("sharedmountpoint")) {
|
|
Long oldPodId = pools.get(0).getPodId();
|
|
throw new ResourceInUseException("Storage pool " + uri + " already in use by another pod (id=" + oldPodId + ")", "StoragePool", uri.toASCIIString());
|
|
}
|
|
|
|
long poolId = _storagePoolDao.getNextInSequence(Long.class, "id");
|
|
String uuid = null;
|
|
if (scheme.equalsIgnoreCase("sharedmountpoint") || scheme.equalsIgnoreCase("clvm")) {
|
|
uuid = UUID.randomUUID().toString();
|
|
} else if (scheme.equalsIgnoreCase("PreSetup")) {
|
|
uuid = hostPath.replace("/", "");
|
|
} else {
|
|
uuid = UUID.nameUUIDFromBytes(new String(storageHost + hostPath).getBytes()).toString();
|
|
}
|
|
|
|
List<StoragePoolVO> spHandles = _storagePoolDao.findIfDuplicatePoolsExistByUUID(uuid);
|
|
if ((spHandles != null) && (spHandles.size() > 0)) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Another active pool with the same uuid already exists");
|
|
}
|
|
throw new ResourceInUseException("Another active pool with the same uuid already exists");
|
|
}
|
|
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("In createPool Setting poolId - " + poolId + " uuid - " + uuid + " zoneId - " + zoneId + " podId - " + podId + " poolName - " + cmd.getStoragePoolName());
|
|
}
|
|
|
|
pool.setId(poolId);
|
|
pool.setUuid(uuid);
|
|
pool.setDataCenterId(cmd.getZoneId());
|
|
pool.setPodId(podId);
|
|
pool.setName(cmd.getStoragePoolName());
|
|
pool.setClusterId(clusterId);
|
|
pool.setStatus(StoragePoolStatus.Up);
|
|
pool = _storagePoolDao.persist(pool, details);
|
|
|
|
if (pool.getPoolType() == StoragePoolType.OCFS2 && !_ocfs2Mgr.prepareNodes(allHosts, pool)) {
|
|
s_logger.warn("Can not create storage pool " + pool + " on cluster " + clusterId);
|
|
_storagePoolDao.expunge(pool.getId());
|
|
return null;
|
|
}
|
|
|
|
boolean success = false;
|
|
for (HostVO h : allHosts) {
|
|
success = createStoragePool(h.getId(), pool);
|
|
if (success) {
|
|
break;
|
|
}
|
|
}
|
|
if (!success) {
|
|
s_logger.warn("Can not create storage pool " + pool + " on cluster " + clusterId);
|
|
_storagePoolDao.expunge(pool.getId());
|
|
return null;
|
|
}
|
|
s_logger.debug("In createPool Adding the pool to each of the hosts");
|
|
List<HostVO> poolHosts = new ArrayList<HostVO>();
|
|
for (HostVO h : allHosts) {
|
|
try {
|
|
connectHostToSharedPool(h.getId(), pool);
|
|
poolHosts.add(h);
|
|
} catch (Exception e) {
|
|
s_logger.warn("Unable to establish a connection between " + h + " and " + pool, e);
|
|
}
|
|
}
|
|
|
|
if (poolHosts.isEmpty()) {
|
|
s_logger.warn("No host can access storage pool " + pool + " on cluster " + clusterId);
|
|
_storagePoolDao.expunge(pool.getId());
|
|
return null;
|
|
} else {
|
|
createCapacityEntry(pool);
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
@Override
|
|
public StoragePoolVO updateStoragePool(UpdateStoragePoolCmd cmd) throws IllegalArgumentException {
|
|
// Input validation
|
|
Long id = cmd.getId();
|
|
List<String> tags = cmd.getTags();
|
|
|
|
StoragePoolVO pool = _storagePoolDao.findById(id);
|
|
if (pool == null) {
|
|
throw new IllegalArgumentException("Unable to find storage pool with ID: " + id);
|
|
}
|
|
|
|
if (tags != null) {
|
|
Map<String, String> details = new HashMap<String, String>();
|
|
for (String tag : tags) {
|
|
tag = tag.trim();
|
|
if (tag.length() > 0 && !details.containsKey(tag)) {
|
|
details.put(tag, "true");
|
|
}
|
|
}
|
|
|
|
_storagePoolDao.updateDetails(id, details);
|
|
}
|
|
|
|
return pool;
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public boolean deletePool(DeletePoolCmd cmd) {
|
|
Long id = cmd.getId();
|
|
boolean deleteFlag = false;
|
|
boolean forced = cmd.isForced();
|
|
|
|
// verify parameters
|
|
StoragePoolVO sPool = _storagePoolDao.findById(id);
|
|
if (sPool == null) {
|
|
s_logger.warn("Unable to find pool:" + id);
|
|
throw new InvalidParameterValueException("Unable to find pool by id " + id);
|
|
}
|
|
if(sPool.getStatus() != StoragePoolStatus.Maintenance){
|
|
s_logger.warn("Unable to delete storage id: " + id +" due to it is not in Maintenance state");
|
|
throw new InvalidParameterValueException("Unable to delete storage due to it is not in Maintenance state, id: " + id);
|
|
}
|
|
if (sPool.getPoolType().equals(StoragePoolType.LVM) || sPool.getPoolType().equals(StoragePoolType.EXT)) {
|
|
s_logger.warn("Unable to delete local storage id:" + id);
|
|
throw new InvalidParameterValueException("Unable to delete local storage id: " + id);
|
|
}
|
|
|
|
Pair<Long, Long> vlms = _volsDao.getCountAndTotalByPool(id);
|
|
if (forced) {
|
|
if (vlms.first() > 0) {
|
|
Pair<Long, Long> nonDstrdVlms = _volsDao.getNonDestroyedCountAndTotalByPool(id);
|
|
if (nonDstrdVlms.first() > 0) {
|
|
throw new CloudRuntimeException("Cannot delete pool " + sPool.getName() + " as there are associated " +
|
|
"non-destroyed vols for this pool");
|
|
}
|
|
//force expunge non-destroyed volumes
|
|
List<VolumeVO> vols = _volsDao.listVolumesToBeDestroyed();
|
|
for (VolumeVO vol : vols) {
|
|
expungeVolume(vol, true);
|
|
}
|
|
}
|
|
} else {
|
|
// Check if the pool has associated volumes in the volumes table
|
|
// If it does , then you cannot delete the pool
|
|
if (vlms.first() > 0) {
|
|
throw new CloudRuntimeException("Cannot delete pool " + sPool.getName() + " as there are associated vols" +
|
|
" for this pool");
|
|
}
|
|
}
|
|
|
|
|
|
// First get the host_id from storage_pool_host_ref for given pool id
|
|
StoragePoolVO lock = _storagePoolDao.acquireInLockTable(sPool.getId());
|
|
|
|
if (lock == null) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Failed to acquire lock when deleting StoragePool with ID: " + sPool.getId());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// mark storage pool as removed (so it can't be used for new volumes creation), release the lock
|
|
boolean isLockReleased = false;
|
|
isLockReleased = _storagePoolDao.releaseFromLockTable(lock.getId());
|
|
s_logger.trace("Released lock for storage pool " + id);
|
|
|
|
// for the given pool id, find all records in the storage_pool_host_ref
|
|
List<StoragePoolHostVO> hostPoolRecords = _storagePoolHostDao.listByPoolId(id);
|
|
Transaction txn = Transaction.currentTxn();
|
|
try {
|
|
// if not records exist, delete the given pool (base case)
|
|
if (hostPoolRecords.size() == 0) {
|
|
|
|
txn.start();
|
|
sPool.setUuid(null);
|
|
_storagePoolDao.update(id, sPool);
|
|
_storagePoolDao.remove(id);
|
|
deletePoolStats(id);
|
|
txn.commit();
|
|
|
|
deleteFlag = true;
|
|
return true;
|
|
} else {
|
|
// Remove the SR associated with the Xenserver
|
|
for (StoragePoolHostVO host : hostPoolRecords) {
|
|
DeleteStoragePoolCommand deleteCmd = new DeleteStoragePoolCommand(sPool);
|
|
final Answer answer = _agentMgr.easySend(host.getHostId(), deleteCmd);
|
|
|
|
if (answer != null && answer.getResult()) {
|
|
deleteFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
if (deleteFlag) {
|
|
// now delete the storage_pool_host_ref and storage_pool records
|
|
txn.start();
|
|
for (StoragePoolHostVO host : hostPoolRecords) {
|
|
_storagePoolHostDao.deleteStoragePoolHostDetails(host.getHostId(), host.getPoolId());
|
|
}
|
|
sPool.setUuid(null);
|
|
_storagePoolDao.update(id, sPool);
|
|
_storagePoolDao.remove(id);
|
|
deletePoolStats(id);
|
|
// Delete op_host_capacity entries
|
|
_capacityDao.removeBy(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED, null, null, null, id);
|
|
txn.commit();
|
|
|
|
s_logger.debug("Storage pool id=" + id + " is removed successfully");
|
|
return true;
|
|
} else {
|
|
// alert that the storage cleanup is required
|
|
s_logger.warn("Failed to Delete storage pool id: " + id);
|
|
_alertMgr.sendAlert(AlertManager.ALERT_TYPE_STORAGE_DELETE, sPool.getDataCenterId(), sPool.getPodId(), "Unable to delete storage pool id= " + id,
|
|
"Delete storage pool command failed. Please check logs.");
|
|
}
|
|
|
|
if (lock != null && !isLockReleased) {
|
|
_storagePoolDao.releaseFromLockTable(lock.getId());
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
@DB
|
|
private boolean deletePoolStats(Long poolId) {
|
|
CapacityVO capacity1 = _capacityDao.findByHostIdType(poolId, CapacityVO.CAPACITY_TYPE_STORAGE);
|
|
CapacityVO capacity2 = _capacityDao.findByHostIdType(poolId, CapacityVO.CAPACITY_TYPE_STORAGE_ALLOCATED);
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
if (capacity1 != null) {
|
|
_capacityDao.remove(capacity1.getId());
|
|
}
|
|
|
|
if (capacity2 != null) {
|
|
_capacityDao.remove(capacity2.getId());
|
|
}
|
|
|
|
txn.commit();
|
|
return true;
|
|
|
|
}
|
|
|
|
@Override
|
|
public boolean createStoragePool(long hostId, StoragePoolVO pool) {
|
|
s_logger.debug("creating pool " + pool.getName() + " on host " + hostId);
|
|
if (pool.getPoolType() != StoragePoolType.NetworkFilesystem && pool.getPoolType() != StoragePoolType.Filesystem && pool.getPoolType() != StoragePoolType.IscsiLUN
|
|
&& pool.getPoolType() != StoragePoolType.Iscsi && pool.getPoolType() != StoragePoolType.VMFS && pool.getPoolType() != StoragePoolType.SharedMountPoint
|
|
&& pool.getPoolType() != StoragePoolType.PreSetup && pool.getPoolType() != StoragePoolType.OCFS2 && pool.getPoolType() != StoragePoolType.RBD && pool.getPoolType() != StoragePoolType.CLVM) {
|
|
s_logger.warn(" Doesn't support storage pool type " + pool.getPoolType());
|
|
return false;
|
|
}
|
|
CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, pool);
|
|
final Answer answer = _agentMgr.easySend(hostId, cmd);
|
|
if (answer != null && answer.getResult()) {
|
|
return true;
|
|
} else {
|
|
_storagePoolDao.expunge(pool.getId());
|
|
String msg = "";
|
|
if (answer != null) {
|
|
msg = "Can not create storage pool through host " + hostId + " due to " + answer.getDetails();
|
|
s_logger.warn(msg);
|
|
} else {
|
|
msg = "Can not create storage pool through host " + hostId + " due to CreateStoragePoolCommand returns null";
|
|
s_logger.warn(msg);
|
|
}
|
|
throw new CloudRuntimeException(msg);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean delPoolFromHost(long hostId) {
|
|
List<StoragePoolHostVO> poolHosts = _poolHostDao.listByHostIdIncludingRemoved(hostId);
|
|
for (StoragePoolHostVO poolHost : poolHosts) {
|
|
s_logger.debug("Deleting pool " + poolHost.getPoolId() + " from host " + hostId);
|
|
_poolHostDao.remove(poolHost.getId());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void connectHostToSharedPool(long hostId, StoragePoolVO pool) throws StorageUnavailableException {
|
|
assert (pool.getPoolType().isShared()) : "Now, did you actually read the name of this method?";
|
|
s_logger.debug("Adding pool " + pool.getName() + " to host " + hostId);
|
|
|
|
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, pool);
|
|
final Answer answer = _agentMgr.easySend(hostId, cmd);
|
|
|
|
if (answer == null) {
|
|
throw new StorageUnavailableException("Unable to get an answer to the modify storage pool command", pool.getId());
|
|
}
|
|
|
|
if (!answer.getResult()) {
|
|
String msg = "Add host failed due to ModifyStoragePoolCommand failed" + answer.getDetails();
|
|
_alertMgr.sendAlert(AlertManager.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
|
|
throw new StorageUnavailableException("Unable establish connection from storage head to storage pool " + pool.getId() + " due to " + answer.getDetails(), pool.getId());
|
|
}
|
|
|
|
assert (answer instanceof ModifyStoragePoolAnswer) : "Well, now why won't you actually return the ModifyStoragePoolAnswer when it's ModifyStoragePoolCommand? Pool=" + pool.getId() + "Host=" + hostId;
|
|
ModifyStoragePoolAnswer mspAnswer = (ModifyStoragePoolAnswer) answer;
|
|
|
|
StoragePoolHostVO poolHost = _poolHostDao.findByPoolHost(pool.getId(), hostId);
|
|
if (poolHost == null) {
|
|
poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
|
|
_poolHostDao.persist(poolHost);
|
|
} else {
|
|
poolHost.setLocalPath(mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
|
|
}
|
|
pool.setAvailableBytes(mspAnswer.getPoolInfo().getAvailableBytes());
|
|
pool.setCapacityBytes(mspAnswer.getPoolInfo().getCapacityBytes());
|
|
_storagePoolDao.update(pool.getId(), pool);
|
|
|
|
s_logger.info("Connection established between " + pool + " host + " + hostId);
|
|
}
|
|
|
|
@Override
|
|
public VolumeVO moveVolume(VolumeVO volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) throws ConcurrentOperationException {
|
|
|
|
// Find a destination storage pool with the specified criteria
|
|
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
|
|
DiskProfile dskCh = new DiskProfile(volume.getId(), volume.getVolumeType(), volume.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(),
|
|
diskOffering.getUseLocalStorage(), diskOffering.isRecreatable(), null);
|
|
dskCh.setHyperType(dataDiskHyperType);
|
|
DataCenterVO destPoolDataCenter = _dcDao.findById(destPoolDcId);
|
|
HostPodVO destPoolPod = _podDao.findById(destPoolPodId);
|
|
StoragePoolVO destPool = findStoragePool(dskCh, destPoolDataCenter, destPoolPod, destPoolClusterId, null, null, new HashSet<StoragePool>());
|
|
String secondaryStorageURL = getSecondaryStorageURL(volume.getDataCenterId());
|
|
|
|
if (destPool == null) {
|
|
throw new CloudRuntimeException("Failed to find a storage pool with enough capacity to move the volume to.");
|
|
}
|
|
if (secondaryStorageURL == null) {
|
|
throw new CloudRuntimeException("Failed to find secondary storage.");
|
|
}
|
|
|
|
List<Volume> vols = new ArrayList<Volume>();
|
|
vols.add(volume);
|
|
migrateVolumes(vols, destPool);
|
|
return _volsDao.findById(volume.getId());
|
|
}
|
|
|
|
|
|
/*
|
|
* Upload the volume to secondary storage.
|
|
*
|
|
*/
|
|
@Override
|
|
@DB
|
|
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPLOAD, eventDescription = "uploading volume", async = true)
|
|
public VolumeVO uploadVolume(UploadVolumeCmd cmd) throws ResourceAllocationException{
|
|
Account caller = UserContext.current().getCaller();
|
|
long ownerId = cmd.getEntityOwnerId();
|
|
Long zoneId = cmd.getZoneId();
|
|
String volumeName = cmd.getVolumeName();
|
|
String url = cmd.getUrl();
|
|
String format = cmd.getFormat();
|
|
|
|
validateVolume(caller, ownerId, zoneId, volumeName, url, format);
|
|
VolumeVO volume = persistVolume(caller, ownerId, zoneId, volumeName, url, cmd.getFormat());
|
|
_downloadMonitor.downloadVolumeToStorage(volume, zoneId, url, cmd.getChecksum(), ImageFormat.valueOf(format.toUpperCase()));
|
|
return volume;
|
|
}
|
|
|
|
private boolean validateVolume(Account caller, long ownerId, Long zoneId, String volumeName, String url, String format) throws ResourceAllocationException{
|
|
|
|
// permission check
|
|
_accountMgr.checkAccess(caller, null, true, _accountMgr.getActiveAccountById(ownerId));
|
|
|
|
// Check that the resource limit for volumes won't be exceeded
|
|
_resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.volume);
|
|
|
|
|
|
// Verify that zone exists
|
|
DataCenterVO zone = _dcDao.findById(zoneId);
|
|
if (zone == null) {
|
|
throw new InvalidParameterValueException("Unable to find zone by id " + zoneId);
|
|
}
|
|
|
|
// Check if zone is disabled
|
|
if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getType())) {
|
|
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
|
|
}
|
|
|
|
if (url.toLowerCase().contains("file://")) {
|
|
throw new InvalidParameterValueException("File:// type urls are currently unsupported");
|
|
}
|
|
|
|
ImageFormat imgfmt = ImageFormat.valueOf(format.toUpperCase());
|
|
if (imgfmt == null) {
|
|
throw new IllegalArgumentException("Image format is incorrect " + format + ". Supported formats are " + EnumUtils.listValues(ImageFormat.values()));
|
|
}
|
|
|
|
String userSpecifiedName = volumeName;
|
|
if (userSpecifiedName == null) {
|
|
userSpecifiedName = getRandomVolumeName();
|
|
}
|
|
if((!url.toLowerCase().endsWith("vhd"))&&(!url.toLowerCase().endsWith("vhd.zip"))
|
|
&&(!url.toLowerCase().endsWith("vhd.bz2"))&&(!url.toLowerCase().endsWith("vhd.gz"))
|
|
&&(!url.toLowerCase().endsWith("qcow2"))&&(!url.toLowerCase().endsWith("qcow2.zip"))
|
|
&&(!url.toLowerCase().endsWith("qcow2.bz2"))&&(!url.toLowerCase().endsWith("qcow2.gz"))
|
|
&&(!url.toLowerCase().endsWith("ova"))&&(!url.toLowerCase().endsWith("ova.zip"))
|
|
&&(!url.toLowerCase().endsWith("ova.bz2"))&&(!url.toLowerCase().endsWith("ova.gz"))
|
|
&&(!url.toLowerCase().endsWith("img"))&&(!url.toLowerCase().endsWith("raw"))){
|
|
throw new InvalidParameterValueException("Please specify a valid " + format.toLowerCase());
|
|
}
|
|
|
|
if ((format.equalsIgnoreCase("vhd") && (!url.toLowerCase().endsWith(".vhd") && !url.toLowerCase().endsWith("vhd.zip") && !url.toLowerCase().endsWith("vhd.bz2") && !url.toLowerCase().endsWith("vhd.gz") ))
|
|
|| (format.equalsIgnoreCase("qcow2") && (!url.toLowerCase().endsWith(".qcow2") && !url.toLowerCase().endsWith("qcow2.zip") && !url.toLowerCase().endsWith("qcow2.bz2") && !url.toLowerCase().endsWith("qcow2.gz") ))
|
|
|| (format.equalsIgnoreCase("ova") && (!url.toLowerCase().endsWith(".ova") && !url.toLowerCase().endsWith("ova.zip") && !url.toLowerCase().endsWith("ova.bz2") && !url.toLowerCase().endsWith("ova.gz")))
|
|
|| (format.equalsIgnoreCase("raw") && (!url.toLowerCase().endsWith(".img") && !url.toLowerCase().endsWith("raw")))) {
|
|
throw new InvalidParameterValueException("Please specify a valid URL. URL:" + url + " is an invalid for the format " + format.toLowerCase());
|
|
}
|
|
validateUrl(url);
|
|
|
|
return false;
|
|
}
|
|
|
|
private String validateUrl(String url){
|
|
try {
|
|
URI uri = new URI(url);
|
|
if ((uri.getScheme() == null) || (!uri.getScheme().equalsIgnoreCase("http")
|
|
&& !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file"))) {
|
|
throw new IllegalArgumentException("Unsupported scheme for url: " + url);
|
|
}
|
|
|
|
int port = uri.getPort();
|
|
if (!(port == 80 || port == 443 || port == -1)) {
|
|
throw new IllegalArgumentException("Only ports 80 and 443 are allowed");
|
|
}
|
|
String host = uri.getHost();
|
|
try {
|
|
InetAddress hostAddr = InetAddress.getByName(host);
|
|
if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress()) {
|
|
throw new IllegalArgumentException("Illegal host specified in url");
|
|
}
|
|
if (hostAddr instanceof Inet6Address) {
|
|
throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")");
|
|
}
|
|
} catch (UnknownHostException uhe) {
|
|
throw new IllegalArgumentException("Unable to resolve " + host);
|
|
}
|
|
|
|
return uri.toString();
|
|
} catch (URISyntaxException e) {
|
|
throw new IllegalArgumentException("Invalid URL " + url);
|
|
}
|
|
|
|
}
|
|
|
|
private VolumeVO persistVolume(Account caller, long ownerId, Long zoneId, String volumeName, String url, String format) {
|
|
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
|
|
VolumeVO volume = new VolumeVO(volumeName, zoneId, -1, -1, -1, new Long(-1), null, null, 0, Volume.Type.DATADISK);
|
|
volume.setPoolId(null);
|
|
volume.setDataCenterId(zoneId);
|
|
volume.setPodId(null);
|
|
volume.setAccountId(ownerId);
|
|
volume.setDomainId(((caller == null) ? Domain.ROOT_DOMAIN : caller.getDomainId()));
|
|
long diskOfferingId = _diskOfferingDao.findByUniqueName("Cloud.com-Custom").getId();
|
|
volume.setDiskOfferingId(diskOfferingId);
|
|
//volume.setSize(size);
|
|
volume.setInstanceId(null);
|
|
volume.setUpdated(new Date());
|
|
volume.setDomainId((caller == null) ? Domain.ROOT_DOMAIN : caller.getDomainId());
|
|
|
|
volume = _volsDao.persist(volume);
|
|
try {
|
|
stateTransitTo(volume, Event.UploadRequested);
|
|
} catch (NoTransitionException e) {
|
|
// TODO Auto-generated catch block
|
|
e.printStackTrace();
|
|
}
|
|
UserContext.current().setEventDetails("Volume Id: " + volume.getId());
|
|
|
|
// Increment resource count during allocation; if actual creation fails, decrement it
|
|
_resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume);
|
|
|
|
txn.commit();
|
|
return volume;
|
|
}
|
|
|
|
|
|
/*
|
|
* Just allocate a volume in the database, don't send the createvolume cmd to hypervisor. The volume will be finally
|
|
* created
|
|
* only when it's attached to a VM.
|
|
*/
|
|
@Override
|
|
@DB
|
|
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true)
|
|
public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationException {
|
|
// FIXME: some of the scheduled event stuff might be missing here...
|
|
Account caller = UserContext.current().getCaller();
|
|
|
|
long ownerId = cmd.getEntityOwnerId();
|
|
|
|
// permission check
|
|
_accountMgr.checkAccess(caller, null, true, _accountMgr.getActiveAccountById(ownerId));
|
|
|
|
// Check that the resource limit for volumes won't be exceeded
|
|
_resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.volume);
|
|
|
|
Long zoneId = cmd.getZoneId();
|
|
Long diskOfferingId = null;
|
|
DiskOfferingVO diskOffering = null;
|
|
Long size = null;
|
|
|
|
// validate input parameters before creating the volume
|
|
if ((cmd.getSnapshotId() == null && cmd.getDiskOfferingId() == null) || (cmd.getSnapshotId() != null && cmd.getDiskOfferingId() != null)) {
|
|
throw new InvalidParameterValueException("Either disk Offering Id or snapshot Id must be passed whilst creating volume");
|
|
}
|
|
|
|
if (cmd.getSnapshotId() == null) {// create a new volume
|
|
|
|
diskOfferingId = cmd.getDiskOfferingId();
|
|
size = cmd.getSize();
|
|
Long sizeInGB = size;
|
|
if (size != null) {
|
|
if (size > 0) {
|
|
size = size * 1024 * 1024 * 1024; // user specify size in GB
|
|
} else {
|
|
throw new InvalidParameterValueException("Disk size must be larger than 0");
|
|
}
|
|
}
|
|
|
|
// Check that the the disk offering is specified
|
|
diskOffering = _diskOfferingDao.findById(diskOfferingId);
|
|
if ((diskOffering == null) || diskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(diskOffering.getType())) {
|
|
throw new InvalidParameterValueException("Please specify a valid disk offering.");
|
|
}
|
|
|
|
if (diskOffering.isCustomized()) {
|
|
if (size == null) {
|
|
throw new InvalidParameterValueException("This disk offering requires a custom size specified");
|
|
}
|
|
if ((sizeInGB < _customDiskOfferingMinSize) || (sizeInGB > _customDiskOfferingMaxSize)) {
|
|
throw new InvalidParameterValueException("Volume size: " + sizeInGB + "GB is out of allowed range. Max: " + _customDiskOfferingMaxSize + " Min:" + _customDiskOfferingMinSize);
|
|
}
|
|
}
|
|
|
|
if (!diskOffering.isCustomized() && size != null) {
|
|
throw new InvalidParameterValueException("This disk offering does not allow custom size");
|
|
}
|
|
|
|
if (diskOffering.getDomainId() == null) {
|
|
// do nothing as offering is public
|
|
} else {
|
|
_configMgr.checkDiskOfferingAccess(caller, diskOffering);
|
|
}
|
|
|
|
if (diskOffering.getDiskSize() > 0) {
|
|
size = diskOffering.getDiskSize();
|
|
}
|
|
|
|
if (!validateVolumeSizeRange(size)) {// convert size from mb to gb for validation
|
|
throw new InvalidParameterValueException("Invalid size for custom volume creation: " + size + " ,max volume size is:" + _maxVolumeSizeInGb);
|
|
}
|
|
} else { // create volume from snapshot
|
|
Long snapshotId = cmd.getSnapshotId();
|
|
SnapshotVO snapshotCheck = _snapshotDao.findById(snapshotId);
|
|
if (snapshotCheck == null) {
|
|
throw new InvalidParameterValueException("unable to find a snapshot with id " + snapshotId);
|
|
}
|
|
|
|
if (snapshotCheck.getStatus() != Snapshot.Status.BackedUp) {
|
|
throw new InvalidParameterValueException("Snapshot id=" + snapshotId + " is not in " + Snapshot.Status.BackedUp + " state yet and can't be used for volume creation");
|
|
}
|
|
|
|
diskOfferingId = snapshotCheck.getDiskOfferingId();
|
|
diskOffering = _diskOfferingDao.findById(diskOfferingId);
|
|
zoneId = snapshotCheck.getDataCenterId();
|
|
size = snapshotCheck.getSize(); // ; disk offering is used for tags purposes
|
|
|
|
// check snapshot permissions
|
|
_accountMgr.checkAccess(caller, null, true, snapshotCheck);
|
|
|
|
/*
|
|
* // bug #11428. Operation not supported if vmware and snapshots parent volume = ROOT
|
|
* if(snapshotCheck.getHypervisorType() == HypervisorType.VMware
|
|
* && _volumeDao.findByIdIncludingRemoved(snapshotCheck.getVolumeId()).getVolumeType() == Type.ROOT){
|
|
* throw new UnsupportedServiceException("operation not supported, snapshot with id " + snapshotId +
|
|
* " is created from ROOT volume");
|
|
* }
|
|
*
|
|
*/
|
|
}
|
|
|
|
// Verify that zone exists
|
|
DataCenterVO zone = _dcDao.findById(zoneId);
|
|
if (zone == null) {
|
|
throw new InvalidParameterValueException("Unable to find zone by id " + zoneId);
|
|
}
|
|
|
|
// Check if zone is disabled
|
|
if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getType())) {
|
|
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
|
|
}
|
|
|
|
// If local storage is disabled then creation of volume with local disk offering not allowed
|
|
if (!zone.isLocalStorageEnabled() && diskOffering.getUseLocalStorage()) {
|
|
throw new InvalidParameterValueException("Zone is not configured to use local storage but volume's disk offering " + diskOffering.getName() + " uses it");
|
|
}
|
|
|
|
// Check that there is appropriate primary storage pool in the specified zone
|
|
List<StoragePoolVO> storagePools = _storagePoolDao.listByDataCenterId(zoneId);
|
|
boolean appropriatePoolExists = false;
|
|
if (!diskOffering.getUseLocalStorage()) {
|
|
for (StoragePoolVO storagePool : storagePools) {
|
|
if (storagePool.isShared()) {
|
|
appropriatePoolExists = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (StoragePoolVO storagePool : storagePools) {
|
|
if (storagePool.isLocal()) {
|
|
appropriatePoolExists = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that there is at least one host in the specified zone
|
|
List<HostVO> hosts = _resourceMgr.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, zoneId);
|
|
if (hosts.isEmpty()) {
|
|
throw new InvalidParameterValueException("There is no workable host in data center id " + zoneId + ", please check hosts' agent status and see if they are disabled");
|
|
}
|
|
|
|
if (!appropriatePoolExists) {
|
|
String storageType = diskOffering.getUseLocalStorage() ? ServiceOffering.StorageType.local.toString() : ServiceOffering.StorageType.shared.toString();
|
|
throw new InvalidParameterValueException("Volume's disk offering uses " + storageType + " storage, please specify a zone that has at least one " + storageType + " primary storage pool.");
|
|
}
|
|
|
|
String userSpecifiedName = cmd.getVolumeName();
|
|
if (userSpecifiedName == null) {
|
|
userSpecifiedName = getRandomVolumeName();
|
|
}
|
|
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
|
|
VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, new Long(-1), null, null, 0, Volume.Type.DATADISK);
|
|
volume.setPoolId(null);
|
|
volume.setDataCenterId(zoneId);
|
|
volume.setPodId(null);
|
|
volume.setAccountId(ownerId);
|
|
volume.setDomainId(((caller == null) ? Domain.ROOT_DOMAIN : caller.getDomainId()));
|
|
volume.setDiskOfferingId(diskOfferingId);
|
|
volume.setSize(size);
|
|
volume.setInstanceId(null);
|
|
volume.setUpdated(new Date());
|
|
volume.setDomainId((caller == null) ? Domain.ROOT_DOMAIN : caller.getDomainId());
|
|
|
|
volume = _volsDao.persist(volume);
|
|
if(cmd.getSnapshotId() == null){
|
|
//for volume created from snapshot, create usage event after volume creation
|
|
UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName(), diskOfferingId, null, size);
|
|
_usageEventDao.persist(usageEvent);
|
|
}
|
|
|
|
UserContext.current().setEventDetails("Volume Id: " + volume.getId());
|
|
|
|
// Increment resource count during allocation; if actual creation fails, decrement it
|
|
_resourceLimitMgr.incrementResourceCount(volume.getAccountId(), ResourceType.volume);
|
|
|
|
txn.commit();
|
|
|
|
return volume;
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", async = true)
|
|
public VolumeVO createVolume(CreateVolumeCmd cmd) {
|
|
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
|
|
boolean created = false;
|
|
|
|
try {
|
|
if (cmd.getSnapshotId() != null) {
|
|
volume = createVolumeFromSnapshot(volume, cmd.getSnapshotId());
|
|
if (volume.getState() == Volume.State.Ready) {
|
|
created = true;
|
|
}
|
|
return volume;
|
|
} else {
|
|
_volsDao.update(volume.getId(), volume);
|
|
created = true;
|
|
}
|
|
|
|
return _volsDao.findById(volume.getId());
|
|
} finally {
|
|
if (!created) {
|
|
s_logger.trace("Decrementing volume resource count for account id=" + volume.getAccountId() + " as volume failed to create on the backend");
|
|
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public boolean destroyVolume(VolumeVO volume) throws ConcurrentOperationException {
|
|
try {
|
|
if (!stateTransitTo(volume, Volume.Event.DestroyRequested)) {
|
|
throw new ConcurrentOperationException("Failed to transit to destroyed state");
|
|
}
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Unable to destoy the volume: " + e.toString());
|
|
return false;
|
|
}
|
|
|
|
long volumeId = volume.getId();
|
|
|
|
// Delete the recurring snapshot policies for this volume.
|
|
_snapshotMgr.deletePoliciesForVolume(volumeId);
|
|
|
|
Long instanceId = volume.getInstanceId();
|
|
VMInstanceVO vmInstance = null;
|
|
if (instanceId != null) {
|
|
vmInstance = _vmInstanceDao.findById(instanceId);
|
|
}
|
|
|
|
if (instanceId == null || (vmInstance.getType().equals(VirtualMachine.Type.User))) {
|
|
// Decrement the resource count for volumes belonging user VM's only
|
|
_resourceLimitMgr.decrementResourceCount(volume.getAccountId(), ResourceType.volume);
|
|
// Log usage event for volumes belonging user VM's only
|
|
UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_DELETE, volume.getAccountId(), volume.getDataCenterId(), volume.getId(), volume.getName());
|
|
_usageEventDao.persist(usageEvent);
|
|
}
|
|
|
|
try {
|
|
if (!stateTransitTo(volume, Volume.Event.OperationSucceeded)) {
|
|
throw new ConcurrentOperationException("Failed to transit state");
|
|
|
|
}
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Unable to change volume state: " + e.toString());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
@Override
|
|
public void createCapacityEntry(StoragePoolVO storagePool) {
|
|
createCapacityEntry(storagePool, Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED, 0);
|
|
}
|
|
|
|
@Override
|
|
public void createCapacityEntry(StoragePoolVO storagePool, short capacityType, long allocated) {
|
|
SearchCriteria<CapacityVO> capacitySC = _capacityDao.createSearchCriteria();
|
|
capacitySC.addAnd("hostOrPoolId", SearchCriteria.Op.EQ, storagePool.getId());
|
|
capacitySC.addAnd("dataCenterId", SearchCriteria.Op.EQ, storagePool.getDataCenterId());
|
|
capacitySC.addAnd("capacityType", SearchCriteria.Op.EQ, capacityType);
|
|
|
|
List<CapacityVO> capacities = _capacityDao.search(capacitySC, null);
|
|
|
|
long totalOverProvCapacity;
|
|
if (storagePool.getPoolType() == StoragePoolType.NetworkFilesystem) {
|
|
totalOverProvCapacity = _overProvisioningFactor.multiply(new BigDecimal(storagePool.getCapacityBytes())).longValue();// All this for the inaccuracy of floats for big number multiplication.
|
|
} else {
|
|
totalOverProvCapacity = storagePool.getCapacityBytes();
|
|
}
|
|
|
|
if (capacities.size() == 0) {
|
|
CapacityVO capacity = new CapacityVO(storagePool.getId(), storagePool.getDataCenterId(), storagePool.getPodId(), storagePool.getClusterId(), allocated, totalOverProvCapacity, capacityType);
|
|
CapacityState capacityState = _configMgr.findClusterAllocationState(ApiDBUtils.findClusterById(storagePool.getClusterId())) == AllocationState.Disabled ?
|
|
CapacityState.Disabled : CapacityState.Enabled;
|
|
capacity.setCapacityState(capacityState);
|
|
_capacityDao.persist(capacity);
|
|
} else {
|
|
CapacityVO capacity = capacities.get(0);
|
|
boolean update = false;
|
|
if (capacity.getTotalCapacity() != totalOverProvCapacity) {
|
|
capacity.setTotalCapacity(totalOverProvCapacity);
|
|
update = true;
|
|
}
|
|
if (allocated != 0) {
|
|
capacity.setUsedCapacity(allocated);
|
|
update = true;
|
|
}
|
|
if (update) {
|
|
_capacityDao.update(capacity.getId(), capacity);
|
|
}
|
|
}
|
|
s_logger.debug("Successfully set Capacity - " + totalOverProvCapacity + " for capacity type - " + capacityType + " , DataCenterId - "
|
|
+ storagePool.getDataCenterId() + ", HostOrPoolId - " + storagePool.getId() + ", PodId " + storagePool.getPodId());
|
|
}
|
|
|
|
@Override
|
|
public List<Long> getUpHostsInPool(long poolId) {
|
|
SearchCriteria<Long> sc = UpHostsInPoolSearch.create();
|
|
sc.setParameters("pool", poolId);
|
|
sc.setJoinParameters("hosts", "status", Status.Up);
|
|
sc.setJoinParameters("hosts", "resourceState", ResourceState.Enabled);
|
|
return _storagePoolHostDao.customSearch(sc, null);
|
|
}
|
|
|
|
@Override
|
|
public Pair<Long, Answer[]> sendToPool(StoragePool pool, long[] hostIdsToTryFirst, List<Long> hostIdsToAvoid, Commands cmds) throws StorageUnavailableException {
|
|
List<Long> hostIds = getUpHostsInPool(pool.getId());
|
|
Collections.shuffle(hostIds);
|
|
if (hostIdsToTryFirst != null) {
|
|
for (int i = hostIdsToTryFirst.length - 1; i >= 0; i--) {
|
|
if (hostIds.remove(hostIdsToTryFirst[i])) {
|
|
hostIds.add(0, hostIdsToTryFirst[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hostIdsToAvoid != null) {
|
|
hostIds.removeAll(hostIdsToAvoid);
|
|
}
|
|
if (hostIds == null || hostIds.isEmpty()) {
|
|
throw new StorageUnavailableException("Unable to send command to the pool " + pool.getId() + " due to there is no enabled hosts up in this cluster", pool.getId());
|
|
}
|
|
for (Long hostId : hostIds) {
|
|
try {
|
|
List<Answer> answers = new ArrayList<Answer>();
|
|
Command[] cmdArray = cmds.toCommands();
|
|
for (Command cmd : cmdArray) {
|
|
long targetHostId = _hvGuruMgr.getGuruProcessedCommandTargetHost(hostId, cmd);
|
|
|
|
answers.add(_agentMgr.send(targetHostId, cmd));
|
|
}
|
|
return new Pair<Long, Answer[]>(hostId, answers.toArray(new Answer[answers.size()]));
|
|
} catch (AgentUnavailableException e) {
|
|
s_logger.debug("Unable to send storage pool command to " + pool + " via " + hostId, e);
|
|
} catch (OperationTimedoutException e) {
|
|
s_logger.debug("Unable to send storage pool command to " + pool + " via " + hostId, e);
|
|
}
|
|
}
|
|
|
|
throw new StorageUnavailableException("Unable to send command to the pool ", pool.getId());
|
|
}
|
|
|
|
@Override
|
|
public Pair<Long, Answer> sendToPool(StoragePool pool, long[] hostIdsToTryFirst, List<Long> hostIdsToAvoid, Command cmd) throws StorageUnavailableException {
|
|
Commands cmds = new Commands(cmd);
|
|
Pair<Long, Answer[]> result = sendToPool(pool, hostIdsToTryFirst, hostIdsToAvoid, cmds);
|
|
return new Pair<Long, Answer>(result.first(), result.second()[0]);
|
|
}
|
|
|
|
@Override
|
|
public void cleanupStorage(boolean recurring) {
|
|
GlobalLock scanLock = GlobalLock.getInternLock("storagemgr.cleanup");
|
|
|
|
try {
|
|
if (scanLock.lock(3)) {
|
|
try {
|
|
// Cleanup primary storage pools
|
|
if (_templateCleanupEnabled) {
|
|
List<StoragePoolVO> storagePools = _storagePoolDao.listAll();
|
|
for (StoragePoolVO pool : storagePools) {
|
|
try {
|
|
|
|
List<VMTemplateStoragePoolVO> unusedTemplatesInPool = _tmpltMgr.getUnusedTemplatesInPool(pool);
|
|
s_logger.debug("Storage pool garbage collector found " + unusedTemplatesInPool.size() + " templates to clean up in storage pool: " + pool.getName());
|
|
for (VMTemplateStoragePoolVO templatePoolVO : unusedTemplatesInPool) {
|
|
if (templatePoolVO.getDownloadState() != VMTemplateStorageResourceAssoc.Status.DOWNLOADED) {
|
|
s_logger.debug("Storage pool garbage collector is skipping templatePoolVO with ID: " + templatePoolVO.getId() + " because it is not completely downloaded.");
|
|
continue;
|
|
}
|
|
|
|
if (!templatePoolVO.getMarkedForGC()) {
|
|
templatePoolVO.setMarkedForGC(true);
|
|
_vmTemplatePoolDao.update(templatePoolVO.getId(), templatePoolVO);
|
|
s_logger.debug("Storage pool garbage collector has marked templatePoolVO with ID: " + templatePoolVO.getId() + " for garbage collection.");
|
|
continue;
|
|
}
|
|
|
|
_tmpltMgr.evictTemplateFromStoragePool(templatePoolVO);
|
|
}
|
|
} catch (Exception e) {
|
|
s_logger.warn("Problem cleaning up primary storage pool " + pool, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanupSecondaryStorage(recurring);
|
|
|
|
List<VolumeVO> vols = _volsDao.listVolumesToBeDestroyed();
|
|
for (VolumeVO vol : vols) {
|
|
try {
|
|
expungeVolume(vol, false);
|
|
} catch (Exception e) {
|
|
s_logger.warn("Unable to destroy " + vol.getId(), e);
|
|
}
|
|
}
|
|
|
|
// remove snapshots in Error state
|
|
List<SnapshotVO> snapshots = _snapshotDao.listAllByStatus(Snapshot.Status.Error);
|
|
for (SnapshotVO snapshotVO : snapshots) {
|
|
try{
|
|
_snapshotDao.expunge(snapshotVO.getId());
|
|
}catch (Exception e) {
|
|
s_logger.warn("Unable to destroy " + snapshotVO.getId(), e);
|
|
}
|
|
}
|
|
|
|
} finally {
|
|
scanLock.unlock();
|
|
}
|
|
}
|
|
} finally {
|
|
scanLock.releaseRef();
|
|
}
|
|
}
|
|
|
|
@DB
|
|
List<Long> findAllVolumeIdInSnapshotTable(Long hostId) {
|
|
String sql = "SELECT volume_id from snapshots WHERE sechost_id=? GROUP BY volume_id";
|
|
List<Long> list = new ArrayList<Long>();
|
|
try {
|
|
Transaction txn = Transaction.currentTxn();
|
|
ResultSet rs = null;
|
|
PreparedStatement pstmt = null;
|
|
pstmt = txn.prepareAutoCloseStatement(sql);
|
|
pstmt.setLong(1, hostId);
|
|
rs = pstmt.executeQuery();
|
|
while (rs.next()) {
|
|
list.add(rs.getLong(1));
|
|
}
|
|
return list;
|
|
} catch (Exception e) {
|
|
s_logger.debug("failed to get all volumes who has snapshots in secondary storage " + hostId + " due to " + e.getMessage());
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
List<String> findAllSnapshotForVolume(Long volumeId) {
|
|
String sql = "SELECT backup_snap_id FROM snapshots WHERE volume_id=? and backup_snap_id is not NULL";
|
|
try {
|
|
Transaction txn = Transaction.currentTxn();
|
|
ResultSet rs = null;
|
|
PreparedStatement pstmt = null;
|
|
pstmt = txn.prepareAutoCloseStatement(sql);
|
|
pstmt.setLong(1, volumeId);
|
|
rs = pstmt.executeQuery();
|
|
List<String> list = new ArrayList<String>();
|
|
while (rs.next()) {
|
|
list.add(rs.getString(1));
|
|
}
|
|
return list;
|
|
} catch (Exception e) {
|
|
s_logger.debug("failed to get all snapshots for a volume " + volumeId + " due to " + e.getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public void cleanupSecondaryStorage(boolean recurring) {
|
|
try {
|
|
// Cleanup templates in secondary storage hosts
|
|
List<HostVO> secondaryStorageHosts = _ssvmMgr.listSecondaryStorageHostsInAllZones();
|
|
for (HostVO secondaryStorageHost : secondaryStorageHosts) {
|
|
try {
|
|
long hostId = secondaryStorageHost.getId();
|
|
List<VMTemplateHostVO> destroyedTemplateHostVOs = _vmTemplateHostDao.listDestroyed(hostId);
|
|
s_logger.debug("Secondary storage garbage collector found " + destroyedTemplateHostVOs.size() + " templates to cleanup on secondary storage host: "
|
|
+ secondaryStorageHost.getName());
|
|
for (VMTemplateHostVO destroyedTemplateHostVO : destroyedTemplateHostVOs) {
|
|
if (!_tmpltMgr.templateIsDeleteable(destroyedTemplateHostVO)) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Not deleting template at: " + destroyedTemplateHostVO);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Deleting template host: " + destroyedTemplateHostVO);
|
|
}
|
|
|
|
String installPath = destroyedTemplateHostVO.getInstallPath();
|
|
|
|
if (installPath != null) {
|
|
Answer answer = _agentMgr.sendToSecStorage(secondaryStorageHost, new DeleteTemplateCommand(secondaryStorageHost.getStorageUrl(), destroyedTemplateHostVO.getInstallPath()));
|
|
|
|
if (answer == null || !answer.getResult()) {
|
|
s_logger.debug("Failed to delete " + destroyedTemplateHostVO + " due to " + ((answer == null) ? "answer is null" : answer.getDetails()));
|
|
} else {
|
|
_vmTemplateHostDao.remove(destroyedTemplateHostVO.getId());
|
|
s_logger.debug("Deleted template at: " + destroyedTemplateHostVO.getInstallPath());
|
|
}
|
|
} else {
|
|
_vmTemplateHostDao.remove(destroyedTemplateHostVO.getId());
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
s_logger.warn("problem cleaning up templates in secondary storage " + secondaryStorageHost, e);
|
|
}
|
|
}
|
|
|
|
// Cleanup snapshot in secondary storage hosts
|
|
for (HostVO secondaryStorageHost : secondaryStorageHosts) {
|
|
try {
|
|
long hostId = secondaryStorageHost.getId();
|
|
List<Long> vIDs = findAllVolumeIdInSnapshotTable(hostId);
|
|
if (vIDs == null) {
|
|
continue;
|
|
}
|
|
for (Long volumeId : vIDs) {
|
|
boolean lock = false;
|
|
try {
|
|
VolumeVO volume = _volsDao.findByIdIncludingRemoved(volumeId);
|
|
if (volume.getRemoved() == null) {
|
|
volume = _volsDao.acquireInLockTable(volumeId, 10);
|
|
if (volume == null) {
|
|
continue;
|
|
}
|
|
lock = true;
|
|
}
|
|
List<String> snapshots = findAllSnapshotForVolume(volumeId);
|
|
if (snapshots == null) {
|
|
continue;
|
|
}
|
|
CleanupSnapshotBackupCommand cmd = new CleanupSnapshotBackupCommand(secondaryStorageHost.getStorageUrl(), secondaryStorageHost.getDataCenterId(), volume.getAccountId(),
|
|
volumeId, snapshots);
|
|
|
|
Answer answer = _agentMgr.sendToSecStorage(secondaryStorageHost, cmd);
|
|
if ((answer == null) || !answer.getResult()) {
|
|
String details = "Failed to cleanup snapshots for volume " + volumeId + " due to " + (answer == null ? "null" : answer.getDetails());
|
|
s_logger.warn(details);
|
|
}
|
|
} catch (Exception e1) {
|
|
s_logger.warn("problem cleaning up snapshots in secondary storage " + secondaryStorageHost, e1);
|
|
} finally {
|
|
if (lock) {
|
|
_volsDao.releaseFromLockTable(volumeId);
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e2) {
|
|
s_logger.warn("problem cleaning up snapshots in secondary storage " + secondaryStorageHost, e2);
|
|
}
|
|
}
|
|
|
|
//CleanUp volumes on Secondary Storage.
|
|
for (HostVO secondaryStorageHost : secondaryStorageHosts) {
|
|
try {
|
|
long hostId = secondaryStorageHost.getId();
|
|
List<VolumeHostVO> destroyedVolumeHostVOs = _volumeHostDao.listDestroyed(hostId);
|
|
s_logger.debug("Secondary storage garbage collector found " + destroyedVolumeHostVOs.size() + " templates to cleanup on secondary storage host: "
|
|
+ secondaryStorageHost.getName());
|
|
for (VolumeHostVO destroyedVolumeHostVO : destroyedVolumeHostVOs) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Deleting volume host: " + destroyedVolumeHostVO);
|
|
}
|
|
|
|
String installPath = destroyedVolumeHostVO.getInstallPath();
|
|
|
|
if (installPath != null) {
|
|
Answer answer = _agentMgr.sendToSecStorage(secondaryStorageHost, new DeleteVolumeCommand(secondaryStorageHost.getStorageUrl(), destroyedVolumeHostVO.getInstallPath()));
|
|
|
|
if (answer == null || !answer.getResult()) {
|
|
s_logger.debug("Failed to delete " + destroyedVolumeHostVO + " due to " + ((answer == null) ? "answer is null" : answer.getDetails()));
|
|
} else {
|
|
_volumeHostDao.remove(destroyedVolumeHostVO.getId());
|
|
s_logger.debug("Deleted volume at: " + destroyedVolumeHostVO.getInstallPath());
|
|
}
|
|
} else {
|
|
_volumeHostDao.remove(destroyedVolumeHostVO.getId());
|
|
}
|
|
}
|
|
|
|
}catch (Exception e2) {
|
|
s_logger.warn("problem cleaning up volumes in secondary storage " + secondaryStorageHost, e2);
|
|
}
|
|
}
|
|
} catch (Exception e3) {
|
|
s_logger.warn("problem cleaning up secondary storage ", e3);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getPrimaryStorageNameLabel(VolumeVO volume) {
|
|
Long poolId = volume.getPoolId();
|
|
|
|
// poolId is null only if volume is destroyed, which has been checked before.
|
|
assert poolId != null;
|
|
StoragePoolVO storagePoolVO = _storagePoolDao.findById(poolId);
|
|
assert storagePoolVO != null;
|
|
return storagePoolVO.getUuid();
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public StoragePoolVO preparePrimaryStorageForMaintenance(Long primaryStorageId) throws ResourceUnavailableException, InsufficientCapacityException {
|
|
Long userId = UserContext.current().getCallerUserId();
|
|
User user = _userDao.findById(userId);
|
|
Account account = UserContext.current().getCaller();
|
|
boolean restart = true;
|
|
StoragePoolVO primaryStorage = null;
|
|
try {
|
|
// 1. Get the primary storage record and perform validation check
|
|
primaryStorage = _storagePoolDao.lockRow(primaryStorageId, true);
|
|
|
|
if (primaryStorage == null) {
|
|
String msg = "Unable to obtain lock on the storage pool record in preparePrimaryStorageForMaintenance()";
|
|
s_logger.error(msg);
|
|
throw new ExecutionException(msg);
|
|
}
|
|
|
|
List<StoragePoolVO> spes = _storagePoolDao.listBy(primaryStorage.getDataCenterId(), primaryStorage.getPodId(), primaryStorage.getClusterId());
|
|
for (StoragePoolVO sp : spes) {
|
|
if (sp.getStatus() == StoragePoolStatus.PrepareForMaintenance) {
|
|
throw new CloudRuntimeException("Only one storage pool in a cluster can be in PrepareForMaintenance mode, " + sp.getId() + " is already in PrepareForMaintenance mode ");
|
|
}
|
|
}
|
|
|
|
if (!primaryStorage.getStatus().equals(StoragePoolStatus.Up) && !primaryStorage.getStatus().equals(StoragePoolStatus.ErrorInMaintenance)) {
|
|
throw new InvalidParameterValueException("Primary storage with id " + primaryStorageId + " is not ready for migration, as the status is:" + primaryStorage.getStatus().toString());
|
|
}
|
|
|
|
List<HostVO> hosts = _resourceMgr.listHostsInClusterByStatus(primaryStorage.getClusterId(), Status.Up);
|
|
if (hosts == null || hosts.size() == 0) {
|
|
primaryStorage.setStatus(StoragePoolStatus.Maintenance);
|
|
_storagePoolDao.update(primaryStorageId, primaryStorage);
|
|
return _storagePoolDao.findById(primaryStorageId);
|
|
} else {
|
|
// set the pool state to prepare for maintenance
|
|
primaryStorage.setStatus(StoragePoolStatus.PrepareForMaintenance);
|
|
_storagePoolDao.update(primaryStorageId, primaryStorage);
|
|
}
|
|
// remove heartbeat
|
|
for (HostVO host : hosts) {
|
|
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(false, primaryStorage);
|
|
final Answer answer = _agentMgr.easySend(host.getId(), cmd);
|
|
if (answer == null || !answer.getResult()) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("ModifyStoragePool false failed due to " + ((answer == null) ? "answer null" : answer.getDetails()));
|
|
}
|
|
} else {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("ModifyStoragePool false secceeded");
|
|
}
|
|
}
|
|
}
|
|
// check to see if other ps exist
|
|
// if they do, then we can migrate over the system vms to them
|
|
// if they dont, then just stop all vms on this one
|
|
List<StoragePoolVO> upPools = _storagePoolDao.listByStatusInZone(primaryStorage.getDataCenterId(), StoragePoolStatus.Up);
|
|
|
|
if (upPools == null || upPools.size() == 0) {
|
|
restart = false;
|
|
}
|
|
|
|
// 2. Get a list of all the ROOT volumes within this storage pool
|
|
List<VolumeVO> allVolumes = _volsDao.findByPoolId(primaryStorageId);
|
|
|
|
// 3. Enqueue to the work queue
|
|
for (VolumeVO volume : allVolumes) {
|
|
VMInstanceVO vmInstance = _vmInstanceDao.findById(volume.getInstanceId());
|
|
|
|
if (vmInstance == null) {
|
|
continue;
|
|
}
|
|
|
|
// enqueue sp work
|
|
if (vmInstance.getState().equals(State.Running) || vmInstance.getState().equals(State.Starting) || vmInstance.getState().equals(State.Stopping)) {
|
|
|
|
try {
|
|
StoragePoolWorkVO work = new StoragePoolWorkVO(vmInstance.getId(), primaryStorageId, false, false, _serverId);
|
|
_storagePoolWorkDao.persist(work);
|
|
} catch (Exception e) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Work record already exists, re-using by re-setting values");
|
|
}
|
|
StoragePoolWorkVO work = _storagePoolWorkDao.findByPoolIdAndVmId(primaryStorageId, vmInstance.getId());
|
|
work.setStartedAfterMaintenance(false);
|
|
work.setStoppedForMaintenance(false);
|
|
work.setManagementServerId(_serverId);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Process the queue
|
|
List<StoragePoolWorkVO> pendingWork = _storagePoolWorkDao.listPendingWorkForPrepareForMaintenanceByPoolId(primaryStorageId);
|
|
|
|
for (StoragePoolWorkVO work : pendingWork) {
|
|
// shut down the running vms
|
|
VMInstanceVO vmInstance = _vmInstanceDao.findById(work.getVmId());
|
|
|
|
if (vmInstance == null) {
|
|
continue;
|
|
}
|
|
|
|
// if the instance is of type consoleproxy, call the console proxy
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.ConsoleProxy)) {
|
|
// call the consoleproxymanager
|
|
ConsoleProxyVO consoleProxy = _consoleProxyDao.findById(vmInstance.getId());
|
|
if (!_vmMgr.advanceStop(consoleProxy, true, user, account)) {
|
|
String errorMsg = "There was an error stopping the console proxy id: " + vmInstance.getId() + " ,cannot enable storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
throw new CloudRuntimeException(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStoppedForMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
|
|
if (restart) {
|
|
|
|
if (_vmMgr.advanceStart(consoleProxy, null, user, account) == null) {
|
|
String errorMsg = "There was an error starting the console proxy id: " + vmInstance.getId() + " on another storage pool, cannot enable primary storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the instance is of type uservm, call the user vm manager
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.User)) {
|
|
UserVmVO userVm = _userVmDao.findById(vmInstance.getId());
|
|
if (!_vmMgr.advanceStop(userVm, true, user, account)) {
|
|
String errorMsg = "There was an error stopping the user vm id: " + vmInstance.getId() + " ,cannot enable storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
throw new CloudRuntimeException(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStoppedForMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
|
|
// if the instance is of type secondary storage vm, call the secondary storage vm manager
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.SecondaryStorageVm)) {
|
|
SecondaryStorageVmVO secStrgVm = _secStrgDao.findById(vmInstance.getId());
|
|
if (!_vmMgr.advanceStop(secStrgVm, true, user, account)) {
|
|
String errorMsg = "There was an error stopping the ssvm id: " + vmInstance.getId() + " ,cannot enable storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
throw new CloudRuntimeException(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStoppedForMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
|
|
if (restart) {
|
|
if (_vmMgr.advanceStart(secStrgVm, null, user, account) == null) {
|
|
String errorMsg = "There was an error starting the ssvm id: " + vmInstance.getId() + " on another storage pool, cannot enable primary storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the instance is of type domain router vm, call the network manager
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.DomainRouter)) {
|
|
DomainRouterVO domR = _domrDao.findById(vmInstance.getId());
|
|
if (!_vmMgr.advanceStop(domR, true, user, account)) {
|
|
String errorMsg = "There was an error stopping the domain router id: " + vmInstance.getId() + " ,cannot enable primary storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
throw new CloudRuntimeException(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStoppedForMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
|
|
if (restart) {
|
|
if (_vmMgr.advanceStart(domR, null, user, account) == null) {
|
|
String errorMsg = "There was an error starting the domain router id: " + vmInstance.getId() + " on another storage pool, cannot enable primary storage maintenance";
|
|
s_logger.warn(errorMsg);
|
|
} else {
|
|
// update work status
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5. Update the status
|
|
primaryStorage.setStatus(StoragePoolStatus.Maintenance);
|
|
_storagePoolDao.update(primaryStorageId, primaryStorage);
|
|
|
|
return _storagePoolDao.findById(primaryStorageId);
|
|
} catch (Exception e) {
|
|
if (e instanceof ExecutionException || e instanceof ResourceUnavailableException) {
|
|
s_logger.error("Exception in enabling primary storage maintenance:", e);
|
|
setPoolStateToError(primaryStorage);
|
|
throw (ResourceUnavailableException) e;
|
|
}
|
|
if (e instanceof InvalidParameterValueException) {
|
|
s_logger.error("Exception in enabling primary storage maintenance:", e);
|
|
setPoolStateToError(primaryStorage);
|
|
throw (InvalidParameterValueException) e;
|
|
}
|
|
if (e instanceof InsufficientCapacityException) {
|
|
s_logger.error("Exception in enabling primary storage maintenance:", e);
|
|
setPoolStateToError(primaryStorage);
|
|
throw (InsufficientCapacityException) e;
|
|
}
|
|
// for everything else
|
|
s_logger.error("Exception in enabling primary storage maintenance:", e);
|
|
setPoolStateToError(primaryStorage);
|
|
throw new CloudRuntimeException(e.getMessage());
|
|
|
|
}
|
|
}
|
|
|
|
private void setPoolStateToError(StoragePoolVO primaryStorage) {
|
|
primaryStorage.setStatus(StoragePoolStatus.ErrorInMaintenance);
|
|
_storagePoolDao.update(primaryStorage.getId(), primaryStorage);
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public StoragePoolVO cancelPrimaryStorageForMaintenance(CancelPrimaryStorageMaintenanceCmd cmd) throws ResourceUnavailableException {
|
|
Long primaryStorageId = cmd.getId();
|
|
Long userId = UserContext.current().getCallerUserId();
|
|
User user = _userDao.findById(userId);
|
|
Account account = UserContext.current().getCaller();
|
|
StoragePoolVO primaryStorage = null;
|
|
try {
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
// 1. Get the primary storage record and perform validation check
|
|
primaryStorage = _storagePoolDao.lockRow(primaryStorageId, true);
|
|
|
|
if (primaryStorage == null) {
|
|
String msg = "Unable to obtain lock on the storage pool in cancelPrimaryStorageForMaintenance()";
|
|
s_logger.error(msg);
|
|
throw new ExecutionException(msg);
|
|
}
|
|
|
|
if (primaryStorage.getStatus().equals(StoragePoolStatus.Up) || primaryStorage.getStatus().equals(StoragePoolStatus.PrepareForMaintenance)) {
|
|
throw new StorageUnavailableException("Primary storage with id " + primaryStorageId + " is not ready to complete migration, as the status is:" + primaryStorage.getStatus().toString(),
|
|
primaryStorageId);
|
|
}
|
|
|
|
// Change the storage state back to up
|
|
primaryStorage.setStatus(StoragePoolStatus.Up);
|
|
_storagePoolDao.update(primaryStorageId, primaryStorage);
|
|
txn.commit();
|
|
List<HostVO> hosts = _resourceMgr.listHostsInClusterByStatus(primaryStorage.getClusterId(), Status.Up);
|
|
if (hosts == null || hosts.size() == 0) {
|
|
return _storagePoolDao.findById(primaryStorageId);
|
|
}
|
|
// add heartbeat
|
|
for (HostVO host : hosts) {
|
|
ModifyStoragePoolCommand msPoolCmd = new ModifyStoragePoolCommand(true, primaryStorage);
|
|
final Answer answer = _agentMgr.easySend(host.getId(), msPoolCmd);
|
|
if (answer == null || !answer.getResult()) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("ModifyStoragePool add failed due to " + ((answer == null) ? "answer null" : answer.getDetails()));
|
|
}
|
|
} else {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("ModifyStoragePool add secceeded");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Get a list of pending work for this queue
|
|
List<StoragePoolWorkVO> pendingWork = _storagePoolWorkDao.listPendingWorkForCancelMaintenanceByPoolId(primaryStorageId);
|
|
|
|
// 3. work through the queue
|
|
for (StoragePoolWorkVO work : pendingWork) {
|
|
|
|
VMInstanceVO vmInstance = _vmInstanceDao.findById(work.getVmId());
|
|
|
|
if (vmInstance == null) {
|
|
continue;
|
|
}
|
|
|
|
// if the instance is of type consoleproxy, call the console proxy
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.ConsoleProxy)) {
|
|
|
|
ConsoleProxyVO consoleProxy = _consoleProxyDao.findById(vmInstance.getId());
|
|
if (_vmMgr.advanceStart(consoleProxy, null, user, account) == null) {
|
|
String msg = "There was an error starting the console proxy id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg);
|
|
throw new ExecutionException(msg);
|
|
} else {
|
|
// update work queue
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
|
|
// if the instance is of type ssvm, call the ssvm manager
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.SecondaryStorageVm)) {
|
|
SecondaryStorageVmVO ssVm = _secStrgDao.findById(vmInstance.getId());
|
|
if (_vmMgr.advanceStart(ssVm, null, user, account) == null) {
|
|
String msg = "There was an error starting the ssvm id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg);
|
|
throw new ExecutionException(msg);
|
|
} else {
|
|
// update work queue
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
|
|
// if the instance is of type ssvm, call the ssvm manager
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.DomainRouter)) {
|
|
DomainRouterVO domR = _domrDao.findById(vmInstance.getId());
|
|
if (_vmMgr.advanceStart(domR, null, user, account) == null) {
|
|
String msg = "There was an error starting the domR id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg);
|
|
throw new ExecutionException(msg);
|
|
} else {
|
|
// update work queue
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
}
|
|
|
|
// if the instance is of type user vm, call the user vm manager
|
|
if (vmInstance.getType().equals(VirtualMachine.Type.User)) {
|
|
UserVmVO userVm = _userVmDao.findById(vmInstance.getId());
|
|
try {
|
|
if (_vmMgr.advanceStart(userVm, null, user, account) == null) {
|
|
|
|
String msg = "There was an error starting the user vm id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg);
|
|
throw new ExecutionException(msg);
|
|
} else {
|
|
// update work queue
|
|
work.setStartedAfterMaintenance(true);
|
|
_storagePoolWorkDao.update(work.getId(), work);
|
|
}
|
|
} catch (StorageUnavailableException e) {
|
|
String msg = "There was an error starting the user vm id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg, e);
|
|
throw new ExecutionException(msg);
|
|
} catch (InsufficientCapacityException e) {
|
|
String msg = "There was an error starting the user vm id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg, e);
|
|
throw new ExecutionException(msg);
|
|
} catch (ConcurrentOperationException e) {
|
|
String msg = "There was an error starting the user vm id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg, e);
|
|
throw new ExecutionException(msg);
|
|
} catch (ExecutionException e) {
|
|
String msg = "There was an error starting the user vm id: " + vmInstance.getId() + " on storage pool, cannot complete primary storage maintenance";
|
|
s_logger.warn(msg, e);
|
|
throw new ExecutionException(msg);
|
|
}
|
|
}
|
|
}
|
|
return primaryStorage;
|
|
} catch (Exception e) {
|
|
setPoolStateToError(primaryStorage);
|
|
if (e instanceof ExecutionException) {
|
|
throw (ResourceUnavailableException) e;
|
|
} else if (e instanceof InvalidParameterValueException) {
|
|
throw (InvalidParameterValueException) e;
|
|
} else {// all other exceptions
|
|
throw new CloudRuntimeException(e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean sendToVmResidesOn(StoragePoolVO storagePool, Command cmd) {
|
|
ClusterVO cluster = _clusterDao.findById(storagePool.getClusterId());
|
|
if ((cluster.getHypervisorType() == HypervisorType.KVM || cluster.getHypervisorType() == HypervisorType.VMware)
|
|
&& ((cmd instanceof ManageSnapshotCommand) || (cmd instanceof BackupSnapshotCommand))) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_DELETE, eventDescription = "deleting volume")
|
|
public boolean deleteVolume(long volumeId, Account caller) throws ConcurrentOperationException {
|
|
|
|
// Check that the volume ID is valid
|
|
VolumeVO volume = _volsDao.findById(volumeId);
|
|
if (volume == null) {
|
|
throw new InvalidParameterValueException("Unable to aquire volume with ID: " + volumeId);
|
|
}
|
|
|
|
if (!_snapshotMgr.canOperateOnVolume(volume)) {
|
|
throw new InvalidParameterValueException("There are snapshot creating on it, Unable to delete the volume");
|
|
}
|
|
|
|
// permission check
|
|
_accountMgr.checkAccess(caller, null, true, volume);
|
|
|
|
// Check that the volume is not currently attached to any VM
|
|
if (volume.getInstanceId() != null) {
|
|
throw new InvalidParameterValueException("Please specify a volume that is not attached to any VM.");
|
|
}
|
|
|
|
// Check that volume is completely Uploaded
|
|
if (volume.getState() == Volume.State.UploadOp){
|
|
VolumeHostVO volumeHost = _volumeHostDao.findByVolumeId(volume.getId());
|
|
if (volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS){
|
|
throw new InvalidParameterValueException("Please specify a volume that is not uploading");
|
|
}
|
|
}
|
|
|
|
// Check that the volume is not already destroyed
|
|
if (volume.getState() != Volume.State.Destroy) {
|
|
if (!destroyVolume(volume)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
try {
|
|
expungeVolume(volume, false);
|
|
} catch (Exception e) {
|
|
s_logger.warn("Failed to expunge volume:", e);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private boolean validateVolumeSizeRange(long size) {
|
|
if (size < 0 || (size > 0 && size < (1024 * 1024 * 1024))) {
|
|
throw new InvalidParameterValueException("Please specify a size of at least 1 Gb.");
|
|
} else if (size > (_maxVolumeSizeInGb * 1024 * 1024 * 1024)) {
|
|
throw new InvalidParameterValueException("volume size " + size + ", but the maximum size allowed is " + _maxVolumeSizeInGb + " Gb.");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected DiskProfile toDiskProfile(VolumeVO vol, DiskOfferingVO offering) {
|
|
return new DiskProfile(vol.getId(), vol.getVolumeType(), vol.getName(), offering.getId(), vol.getSize(), offering.getTagsArray(), offering.getUseLocalStorage(), offering.isRecreatable(),
|
|
vol.getTemplateId());
|
|
}
|
|
|
|
@Override
|
|
public <T extends VMInstanceVO> DiskProfile allocateRawVolume(Type type, String name, DiskOfferingVO offering, Long size, T vm, Account owner) {
|
|
if (size == null) {
|
|
size = offering.getDiskSize();
|
|
} else {
|
|
size = (size * 1024 * 1024 * 1024);
|
|
}
|
|
VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterIdToDeployIn(), owner.getDomainId(), owner.getId(), offering.getId(), size);
|
|
if (vm != null) {
|
|
vol.setInstanceId(vm.getId());
|
|
}
|
|
|
|
if (type.equals(Type.ROOT)) {
|
|
vol.setDeviceId(0l);
|
|
} else {
|
|
vol.setDeviceId(1l);
|
|
}
|
|
|
|
vol = _volsDao.persist(vol);
|
|
|
|
// Save usage event and update resource count for user vm volumes
|
|
if (vm instanceof UserVm) {
|
|
|
|
UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offering.getId(), null, size);
|
|
_usageEventDao.persist(usageEvent);
|
|
|
|
_resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.volume);
|
|
}
|
|
return toDiskProfile(vol, offering);
|
|
}
|
|
|
|
@Override
|
|
public <T extends VMInstanceVO> DiskProfile allocateTemplatedVolume(Type type, String name, DiskOfferingVO offering, VMTemplateVO template, T vm, Account owner) {
|
|
assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template really....";
|
|
|
|
SearchCriteria<VMTemplateHostVO> sc = HostTemplateStatesSearch.create();
|
|
sc.setParameters("id", template.getId());
|
|
sc.setParameters("state", com.cloud.storage.VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
|
|
sc.setJoinParameters("host", "dcId", vm.getDataCenterIdToDeployIn());
|
|
List<VMTemplateSwiftVO> tsvs = _vmTemplateSwiftDao.listByTemplateId(template.getId());
|
|
Long size = null;
|
|
if (tsvs != null && tsvs.size() > 0) {
|
|
size = tsvs.get(0).getSize();
|
|
}
|
|
|
|
if (size == null && _s3Mgr.isS3Enabled()) {
|
|
VMTemplateS3VO vmTemplateS3VO = _vmTemplateS3Dao.findOneByTemplateId(template.getId());
|
|
if (vmTemplateS3VO != null) {
|
|
size = vmTemplateS3VO.getSize();
|
|
}
|
|
}
|
|
|
|
if (size == null) {
|
|
List<VMTemplateHostVO> sss = _vmTemplateHostDao.search(sc, null);
|
|
if (sss == null || sss.size() == 0) {
|
|
throw new CloudRuntimeException("Template " + template.getName() + " has not been completely downloaded to zone " + vm.getDataCenterIdToDeployIn());
|
|
}
|
|
size = sss.get(0).getSize();
|
|
}
|
|
|
|
VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterIdToDeployIn(), owner.getDomainId(), owner.getId(), offering.getId(), size);
|
|
if (vm != null) {
|
|
vol.setInstanceId(vm.getId());
|
|
}
|
|
vol.setTemplateId(template.getId());
|
|
|
|
if (type.equals(Type.ROOT)) {
|
|
vol.setDeviceId(0l);
|
|
if (!vm.getType().equals(VirtualMachine.Type.User)) {
|
|
vol.setRecreatable(true);
|
|
}
|
|
} else {
|
|
vol.setDeviceId(1l);
|
|
}
|
|
|
|
vol = _volsDao.persist(vol);
|
|
|
|
// Create event and update resource count for volumes if vm is a user vm
|
|
if (vm instanceof UserVm) {
|
|
|
|
Long offeringId = null;
|
|
|
|
if (offering.getType() == DiskOfferingVO.Type.Disk) {
|
|
offeringId = offering.getId();
|
|
}
|
|
|
|
UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offeringId, template.getId(),
|
|
vol.getSize());
|
|
_usageEventDao.persist(usageEvent);
|
|
|
|
_resourceLimitMgr.incrementResourceCount(vm.getAccountId(), ResourceType.volume);
|
|
}
|
|
return toDiskProfile(vol, offering);
|
|
}
|
|
|
|
@Override
|
|
public void prepareForMigration(VirtualMachineProfile<? extends VirtualMachine> vm, DeployDestination dest) {
|
|
List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId());
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Preparing " + vols.size() + " volumes for " + vm);
|
|
}
|
|
|
|
for (VolumeVO vol : vols) {
|
|
StoragePool pool = _storagePoolDao.findById(vol.getPoolId());
|
|
vm.addDisk(new VolumeTO(vol, pool));
|
|
}
|
|
|
|
if (vm.getType() == VirtualMachine.Type.User) {
|
|
UserVmVO userVM = (UserVmVO) vm.getVirtualMachine();
|
|
if (userVM.getIsoId() != null) {
|
|
Pair<String, String> isoPathPair = getAbsoluteIsoPath(userVM.getIsoId(), userVM.getDataCenterIdToDeployIn());
|
|
if (isoPathPair != null) {
|
|
String isoPath = isoPathPair.first();
|
|
VolumeTO iso = new VolumeTO(vm.getId(), Volume.Type.ISO, StoragePoolType.ISO, null, null, null, isoPath, 0, null, null);
|
|
vm.addDisk(iso);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@DB
|
|
@Override
|
|
public Volume migrateVolume(Long volumeId, Long storagePoolId) throws ConcurrentOperationException {
|
|
VolumeVO vol = _volsDao.findById(volumeId);
|
|
if (vol == null) {
|
|
throw new InvalidParameterValueException("Failed to find the volume id: " + volumeId);
|
|
}
|
|
|
|
if (vol.getState() != Volume.State.Ready) {
|
|
throw new InvalidParameterValueException("Volume must be in ready state");
|
|
}
|
|
|
|
if (vol.getInstanceId() != null) {
|
|
throw new InvalidParameterValueException("Volume needs to be dettached from VM");
|
|
}
|
|
|
|
StoragePool destPool = _storagePoolDao.findById(storagePoolId);
|
|
if (destPool == null) {
|
|
throw new InvalidParameterValueException("Failed to find the destination storage pool: " + storagePoolId);
|
|
}
|
|
|
|
if (!volumeOnSharedStoragePool(vol)) {
|
|
throw new InvalidParameterValueException("Migration of volume from local storage pool is not supported");
|
|
}
|
|
|
|
List<Volume> vols = new ArrayList<Volume>();
|
|
vols.add(vol);
|
|
|
|
migrateVolumes(vols, destPool);
|
|
return vol;
|
|
}
|
|
|
|
@DB
|
|
public boolean migrateVolumes(List<Volume> volumes, StoragePool destPool) throws ConcurrentOperationException {
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
|
|
boolean transitResult = false;
|
|
long checkPointTaskId = -1;
|
|
try {
|
|
List<Long> volIds = new ArrayList<Long>();
|
|
for (Volume volume : volumes) {
|
|
if (!_snapshotMgr.canOperateOnVolume((VolumeVO) volume)) {
|
|
throw new CloudRuntimeException("There are snapshots creating on this volume, can not move this volume");
|
|
}
|
|
|
|
try {
|
|
if (!stateTransitTo(volume, Volume.Event.MigrationRequested)) {
|
|
throw new ConcurrentOperationException("Failed to transit volume state");
|
|
}
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Failed to set state into migrate: " + e.toString());
|
|
throw new CloudRuntimeException("Failed to set state into migrate: " + e.toString());
|
|
}
|
|
volIds.add(volume.getId());
|
|
}
|
|
|
|
checkPointTaskId = _checkPointMgr.pushCheckPoint(new StorageMigrationCleanupMaid(StorageMigrationCleanupMaid.StorageMigrationState.MIGRATING, volIds));
|
|
transitResult = true;
|
|
} finally {
|
|
if (!transitResult) {
|
|
txn.rollback();
|
|
} else {
|
|
txn.commit();
|
|
}
|
|
}
|
|
|
|
// At this stage, nobody can modify volumes. Send the copyvolume command
|
|
List<Pair<StoragePoolVO, DestroyCommand>> destroyCmds = new ArrayList<Pair<StoragePoolVO, DestroyCommand>>();
|
|
List<CopyVolumeAnswer> answers = new ArrayList<CopyVolumeAnswer>();
|
|
try {
|
|
for (Volume volume : volumes) {
|
|
String secondaryStorageURL = getSecondaryStorageURL(volume.getDataCenterId());
|
|
StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId());
|
|
CopyVolumeCommand cvCmd = new CopyVolumeCommand(volume.getId(), volume.getPath(), srcPool, secondaryStorageURL, true, _copyvolumewait);
|
|
CopyVolumeAnswer cvAnswer;
|
|
try {
|
|
cvAnswer = (CopyVolumeAnswer) sendToPool(srcPool, cvCmd);
|
|
} catch (StorageUnavailableException e1) {
|
|
throw new CloudRuntimeException("Failed to copy the volume from the source primary storage pool to secondary storage.", e1);
|
|
}
|
|
|
|
if (cvAnswer == null || !cvAnswer.getResult()) {
|
|
throw new CloudRuntimeException("Failed to copy the volume from the source primary storage pool to secondary storage.");
|
|
}
|
|
|
|
String secondaryStorageVolumePath = cvAnswer.getVolumePath();
|
|
|
|
// Copy the volume from secondary storage to the destination storage
|
|
// pool
|
|
cvCmd = new CopyVolumeCommand(volume.getId(), secondaryStorageVolumePath, destPool, secondaryStorageURL, false, _copyvolumewait);
|
|
try {
|
|
cvAnswer = (CopyVolumeAnswer) sendToPool(destPool, cvCmd);
|
|
} catch (StorageUnavailableException e1) {
|
|
throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool.");
|
|
}
|
|
|
|
if (cvAnswer == null || !cvAnswer.getResult()) {
|
|
throw new CloudRuntimeException("Failed to copy the volume from secondary storage to the destination primary storage pool.");
|
|
}
|
|
|
|
answers.add(cvAnswer);
|
|
destroyCmds.add(new Pair<StoragePoolVO, DestroyCommand>(srcPool, new DestroyCommand(srcPool, volume, null)));
|
|
}
|
|
} finally {
|
|
if (answers.size() != volumes.size()) {
|
|
// this means one of copying volume failed
|
|
for (Volume volume : volumes) {
|
|
try {
|
|
stateTransitTo(volume, Volume.Event.OperationFailed);
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Failed to change volume state: " + e.toString());
|
|
}
|
|
}
|
|
_checkPointMgr.popCheckPoint(checkPointTaskId);
|
|
} else {
|
|
// Need a transaction, make sure all the volumes get migrated to new storage pool
|
|
txn = Transaction.currentTxn();
|
|
txn.start();
|
|
|
|
transitResult = false;
|
|
try {
|
|
for (int i = 0; i < volumes.size(); i++) {
|
|
CopyVolumeAnswer answer = answers.get(i);
|
|
VolumeVO volume = (VolumeVO) volumes.get(i);
|
|
Long oldPoolId = volume.getPoolId();
|
|
volume.setPath(answer.getVolumePath());
|
|
volume.setFolder(destPool.getPath());
|
|
volume.setPodId(destPool.getPodId());
|
|
volume.setPoolId(destPool.getId());
|
|
volume.setLastPoolId(oldPoolId);
|
|
volume.setPodId(destPool.getPodId());
|
|
try {
|
|
stateTransitTo(volume, Volume.Event.OperationSucceeded);
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Failed to change volume state: " + e.toString());
|
|
throw new CloudRuntimeException("Failed to change volume state: " + e.toString());
|
|
}
|
|
}
|
|
transitResult = true;
|
|
try {
|
|
_checkPointMgr.popCheckPoint(checkPointTaskId);
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
} finally {
|
|
if (!transitResult) {
|
|
txn.rollback();
|
|
} else {
|
|
txn.commit();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// all the volumes get migrated to new storage pool, need to delete the copy on old storage pool
|
|
for (Pair<StoragePoolVO, DestroyCommand> cmd : destroyCmds) {
|
|
try {
|
|
Answer cvAnswer = sendToPool(cmd.first(), cmd.second());
|
|
} catch (StorageUnavailableException e) {
|
|
s_logger.debug("Unable to delete the old copy on storage pool: " + e.toString());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean StorageMigration(VirtualMachineProfile<? extends VirtualMachine> vm, StoragePool destPool) throws ConcurrentOperationException {
|
|
List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId());
|
|
List<Volume> volumesNeedToMigrate = new ArrayList<Volume>();
|
|
|
|
for (VolumeVO volume : vols) {
|
|
if (volume.getState() != Volume.State.Ready) {
|
|
s_logger.debug("volume: " + volume.getId() + " is in " + volume.getState() + " state");
|
|
throw new CloudRuntimeException("volume: " + volume.getId() + " is in " + volume.getState() + " state");
|
|
}
|
|
|
|
if (volume.getPoolId() == destPool.getId()) {
|
|
s_logger.debug("volume: " + volume.getId() + " is on the same storage pool: " + destPool.getId());
|
|
continue;
|
|
}
|
|
|
|
volumesNeedToMigrate.add(volume);
|
|
}
|
|
|
|
if (volumesNeedToMigrate.isEmpty()) {
|
|
s_logger.debug("No volume need to be migrated");
|
|
return true;
|
|
}
|
|
|
|
return migrateVolumes(volumesNeedToMigrate, destPool);
|
|
}
|
|
|
|
@Override
|
|
public void prepare(VirtualMachineProfile<? extends VirtualMachine> vm, DeployDestination dest) throws StorageUnavailableException, InsufficientStorageCapacityException {
|
|
|
|
if (dest == null) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("DeployDestination cannot be null, cannot prepare Volumes for the vm: " + vm);
|
|
}
|
|
throw new CloudRuntimeException("Unable to prepare Volume for vm because DeployDestination is null, vm:" + vm);
|
|
}
|
|
List<VolumeVO> vols = _volsDao.findUsableVolumesForInstance(vm.getId());
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Checking if we need to prepare " + vols.size() + " volumes for " + vm);
|
|
}
|
|
|
|
boolean recreate = _recreateSystemVmEnabled;
|
|
|
|
List<VolumeVO> recreateVols = new ArrayList<VolumeVO>(vols.size());
|
|
|
|
for (VolumeVO vol : vols) {
|
|
StoragePool assignedPool = null;
|
|
if (dest.getStorageForDisks() != null) {
|
|
assignedPool = dest.getStorageForDisks().get(vol);
|
|
}
|
|
if (assignedPool == null && recreate) {
|
|
assignedPool = _storagePoolDao.findById(vol.getPoolId());
|
|
|
|
}
|
|
if (assignedPool != null || recreate) {
|
|
Volume.State state = vol.getState();
|
|
if (state == Volume.State.Allocated || state == Volume.State.Creating) {
|
|
recreateVols.add(vol);
|
|
} else {
|
|
if (vol.isRecreatable()) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Volume " + vol + " will be recreated on storage pool " + assignedPool + " assigned by deploymentPlanner");
|
|
}
|
|
recreateVols.add(vol);
|
|
} else {
|
|
if (assignedPool.getId() != vol.getPoolId()) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Mismatch in storage pool " + assignedPool + " assigned by deploymentPlanner and the one associated with volume " + vol);
|
|
}
|
|
DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId());
|
|
if (diskOffering.getUseLocalStorage())
|
|
{
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Local volume " + vol + " will be recreated on storage pool " + assignedPool + " assigned by deploymentPlanner");
|
|
}
|
|
recreateVols.add(vol);
|
|
} else {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Shared volume " + vol + " will be migrated on storage pool " + assignedPool + " assigned by deploymentPlanner");
|
|
}
|
|
try {
|
|
List<Volume> volumesToMigrate = new ArrayList<Volume>();
|
|
volumesToMigrate.add(vol);
|
|
migrateVolumes(volumesToMigrate, assignedPool);
|
|
vm.addDisk(new VolumeTO(vol, assignedPool));
|
|
} catch (ConcurrentOperationException e) {
|
|
throw new CloudRuntimeException("Migration of volume " + vol + " to storage pool " + assignedPool + " failed", e);
|
|
}
|
|
}
|
|
} else {
|
|
StoragePoolVO pool = _storagePoolDao.findById(vol.getPoolId());
|
|
vm.addDisk(new VolumeTO(vol, pool));
|
|
}
|
|
|
|
}
|
|
}
|
|
} else {
|
|
if (vol.getPoolId() == null) {
|
|
throw new StorageUnavailableException("Volume has no pool associate and also no storage pool assigned in DeployDestination, Unable to create " + vol, Volume.class, vol.getId());
|
|
}
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("No need to recreate the volume: " + vol + ", since it already has a pool assigned: " + vol.getPoolId() + ", adding disk to VM");
|
|
}
|
|
StoragePoolVO pool = _storagePoolDao.findById(vol.getPoolId());
|
|
vm.addDisk(new VolumeTO(vol, pool));
|
|
}
|
|
}
|
|
|
|
for (VolumeVO vol : recreateVols) {
|
|
VolumeVO newVol;
|
|
StoragePool existingPool = null;
|
|
if (recreate && (dest.getStorageForDisks() == null || dest.getStorageForDisks().get(vol) == null)) {
|
|
existingPool = _storagePoolDao.findById(vol.getPoolId());
|
|
s_logger.debug("existing pool: " + existingPool.getId());
|
|
}
|
|
|
|
if (vol.getState() == Volume.State.Allocated || vol.getState() == Volume.State.Creating) {
|
|
newVol = vol;
|
|
} else {
|
|
newVol = switchVolume(vol, vm);
|
|
// update the volume->storagePool map since volumeId has changed
|
|
if (dest.getStorageForDisks() != null && dest.getStorageForDisks().containsKey(vol)) {
|
|
StoragePool poolWithOldVol = dest.getStorageForDisks().get(vol);
|
|
dest.getStorageForDisks().put(newVol, poolWithOldVol);
|
|
dest.getStorageForDisks().remove(vol);
|
|
}
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Created new volume " + newVol + " for old volume " + vol);
|
|
}
|
|
}
|
|
|
|
try {
|
|
stateTransitTo(newVol, Volume.Event.CreateRequested);
|
|
} catch (NoTransitionException e) {
|
|
throw new CloudRuntimeException("Unable to create " + e.toString());
|
|
}
|
|
|
|
Pair<VolumeTO, StoragePool> created = createVolume(newVol, _diskOfferingDao.findById(newVol.getDiskOfferingId()), vm, vols, dest, existingPool);
|
|
|
|
if (created == null) {
|
|
Long poolId = newVol.getPoolId();
|
|
newVol.setPoolId(null);
|
|
try {
|
|
stateTransitTo(newVol, Volume.Event.OperationFailed);
|
|
} catch (NoTransitionException e) {
|
|
throw new CloudRuntimeException("Unable to update the failure on a volume: " + newVol, e);
|
|
}
|
|
throw new StorageUnavailableException("Unable to create " + newVol, poolId == null ? -1L : poolId);
|
|
}
|
|
created.first().setDeviceId(newVol.getDeviceId().intValue());
|
|
newVol.setFolder(created.second().getPath());
|
|
newVol.setPath(created.first().getPath());
|
|
newVol.setSize(created.first().getSize());
|
|
newVol.setPoolType(created.second().getPoolType());
|
|
newVol.setPodId(created.second().getPodId());
|
|
try {
|
|
stateTransitTo(newVol, Volume.Event.OperationSucceeded);
|
|
} catch (NoTransitionException e) {
|
|
throw new CloudRuntimeException("Unable to update an CREATE operation succeeded on volume " + newVol, e);
|
|
}
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Volume " + newVol + " is created on " + created.second());
|
|
}
|
|
|
|
vm.addDisk(created.first());
|
|
}
|
|
}
|
|
|
|
@DB
|
|
protected VolumeVO switchVolume(VolumeVO existingVolume, VirtualMachineProfile<? extends VirtualMachine> vm) throws StorageUnavailableException {
|
|
Transaction txn = Transaction.currentTxn();
|
|
|
|
Long templateIdToUse = null;
|
|
Long volTemplateId = existingVolume.getTemplateId();
|
|
long vmTemplateId = vm.getTemplateId();
|
|
if (volTemplateId != null && volTemplateId.longValue() != vmTemplateId) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("switchVolume: Old Volume's templateId: " + volTemplateId + " does not match the VM's templateId: " + vmTemplateId + ", updating templateId in the new Volume");
|
|
}
|
|
templateIdToUse = vmTemplateId;
|
|
}
|
|
|
|
txn.start();
|
|
VolumeVO newVolume = allocateDuplicateVolume(existingVolume, templateIdToUse);
|
|
// In case of Vmware if vm reference is not removed then during root disk cleanup
|
|
// the vm also gets deleted, so remove the reference
|
|
if (vm.getHypervisorType() == HypervisorType.VMware) {
|
|
_volsDao.detachVolume(existingVolume.getId());
|
|
}
|
|
try {
|
|
stateTransitTo(existingVolume, Volume.Event.DestroyRequested);
|
|
} catch (NoTransitionException e) {
|
|
s_logger.debug("Unable to destroy existing volume: " + e.toString());
|
|
}
|
|
txn.commit();
|
|
return newVolume;
|
|
|
|
}
|
|
|
|
public Pair<VolumeTO, StoragePool> createVolume(VolumeVO toBeCreated, DiskOfferingVO offering, VirtualMachineProfile<? extends VirtualMachine> vm, List<? extends Volume> alreadyCreated,
|
|
DeployDestination dest, StoragePool sPool) throws StorageUnavailableException {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Creating volume: " + toBeCreated);
|
|
}
|
|
DiskProfile diskProfile = new DiskProfile(toBeCreated, offering, vm.getHypervisorType());
|
|
|
|
VMTemplateVO template = null;
|
|
if (toBeCreated.getTemplateId() != null) {
|
|
template = _templateDao.findById(toBeCreated.getTemplateId());
|
|
}
|
|
|
|
StoragePool pool = null;
|
|
if (sPool != null) {
|
|
pool = sPool;
|
|
} else {
|
|
pool = dest.getStorageForDisks().get(toBeCreated);
|
|
}
|
|
|
|
if (pool != null) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Trying to create in " + pool);
|
|
}
|
|
toBeCreated.setPoolId(pool.getId());
|
|
try {
|
|
stateTransitTo(toBeCreated, Volume.Event.OperationRetry);
|
|
} catch (NoTransitionException e) {
|
|
throw new CloudRuntimeException("Unable to retry a create operation on volume " + toBeCreated);
|
|
}
|
|
|
|
CreateCommand cmd = null;
|
|
VMTemplateStoragePoolVO tmpltStoredOn = null;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (template != null && template.getFormat() != Storage.ImageFormat.ISO) {
|
|
if (pool.getPoolType() == StoragePoolType.CLVM) {
|
|
//prepareISOForCreate does what we need, which is to tell us where the template is
|
|
VMTemplateHostVO tmpltHostOn = _tmpltMgr.prepareISOForCreate(template, pool);
|
|
if (tmpltHostOn == null) {
|
|
s_logger.debug("cannot find template " + template.getId() + " " + template.getName());
|
|
return null;
|
|
}
|
|
HostVO secondaryStorageHost = _hostDao.findById(tmpltHostOn.getHostId());
|
|
String tmpltHostUrl = secondaryStorageHost.getStorageUrl();
|
|
String fullTmpltUrl = tmpltHostUrl + "/" + tmpltHostOn.getInstallPath();
|
|
cmd = new CreateCommand(diskProfile, fullTmpltUrl, new StorageFilerTO(pool));
|
|
} else {
|
|
tmpltStoredOn = _tmpltMgr.prepareTemplateForCreate(template, pool);
|
|
if (tmpltStoredOn == null) {
|
|
s_logger.debug("Cannot use this pool " + pool + " because we can't propagate template " + template);
|
|
return null;
|
|
}
|
|
cmd = new CreateCommand(diskProfile, tmpltStoredOn.getLocalDownloadPath(), new StorageFilerTO(pool));
|
|
}
|
|
} else {
|
|
if (template != null && Storage.ImageFormat.ISO == template.getFormat()) {
|
|
VMTemplateHostVO tmpltHostOn = _tmpltMgr.prepareISOForCreate(template, pool);
|
|
if (tmpltHostOn == null) {
|
|
throw new CloudRuntimeException("Did not find ISO in secondry storage in zone " + pool.getDataCenterId());
|
|
}
|
|
}
|
|
cmd = new CreateCommand(diskProfile, new StorageFilerTO(pool));
|
|
}
|
|
long[] hostIdsToTryFirst = { dest.getHost().getId() };
|
|
Answer answer = sendToPool(pool, hostIdsToTryFirst, cmd);
|
|
if (answer.getResult()) {
|
|
CreateAnswer createAnswer = (CreateAnswer) answer;
|
|
return new Pair<VolumeTO, StoragePool>(createAnswer.getVolume(), pool);
|
|
} else {
|
|
if (tmpltStoredOn != null && (answer instanceof CreateAnswer) && ((CreateAnswer) answer).templateReloadRequested()) {
|
|
if (!_tmpltMgr.resetTemplateDownloadStateOnPool(tmpltStoredOn.getId())) {
|
|
break; // break out of template-redeploy retry loop
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Unable to create volume " + toBeCreated);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void release(VirtualMachineProfile<? extends VMInstanceVO> profile) {
|
|
// add code here
|
|
}
|
|
|
|
public void expungeVolume(VolumeVO vol, boolean force) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Expunging " + vol);
|
|
}
|
|
|
|
//Find out if the volume is present on secondary storage
|
|
VolumeHostVO volumeHost = _volumeHostDao.findByVolumeId(vol.getId());
|
|
if(volumeHost != null){
|
|
if (volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED){
|
|
HostVO ssHost = _hostDao.findById(volumeHost.getHostId());
|
|
DeleteVolumeCommand dtCommand = new DeleteVolumeCommand(ssHost.getStorageUrl(), volumeHost.getInstallPath());
|
|
Answer answer = _agentMgr.sendToSecStorage(ssHost, dtCommand);
|
|
if (answer == null || !answer.getResult()) {
|
|
s_logger.debug("Failed to delete " + volumeHost + " due to " + ((answer == null) ? "answer is null" : answer.getDetails()));
|
|
return;
|
|
}
|
|
}else if(volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS){
|
|
s_logger.debug("Volume: " + vol.getName() + " is currently being uploaded; cant' delete it.");
|
|
throw new CloudRuntimeException("Please specify a volume that is not currently being uploaded.");
|
|
}
|
|
_volumeHostDao.remove(volumeHost.getId());
|
|
_volumeDao.remove(vol.getId());
|
|
return;
|
|
}
|
|
|
|
String vmName = null;
|
|
if (vol.getVolumeType() == Type.ROOT && vol.getInstanceId() != null) {
|
|
VirtualMachine vm = _vmInstanceDao.findByIdIncludingRemoved(vol.getInstanceId());
|
|
if (vm != null) {
|
|
vmName = vm.getInstanceName();
|
|
}
|
|
}
|
|
|
|
String volumePath = vol.getPath();
|
|
Long poolId = vol.getPoolId();
|
|
if (poolId == null || volumePath == null || volumePath.trim().isEmpty()) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Marking volume that was never created as destroyed: " + vol);
|
|
}
|
|
_volsDao.remove(vol.getId());
|
|
return;
|
|
}
|
|
|
|
StoragePoolVO pool = _storagePoolDao.findById(poolId);
|
|
if (pool == null) {
|
|
s_logger.debug("Removing volume as storage pool is gone: " + poolId);
|
|
_volsDao.remove(vol.getId());
|
|
return;
|
|
}
|
|
|
|
DestroyCommand cmd = new DestroyCommand(pool, vol, vmName);
|
|
boolean removeVolume = false;
|
|
try {
|
|
Answer answer = sendToPool(pool, cmd);
|
|
if (answer != null && answer.getResult()) {
|
|
removeVolume = true;
|
|
} else {
|
|
s_logger.info("Will retry delete of " + vol + " from " + poolId);
|
|
}
|
|
} catch (StorageUnavailableException e) {
|
|
if (force) {
|
|
s_logger.info("Storage is unavailable currently, but marking volume id=" + vol.getId() + " as expunged anyway due to force=true");
|
|
removeVolume = true;
|
|
} else {
|
|
s_logger.info("Storage is unavailable currently. Will retry delete of " + vol + " from " + poolId);
|
|
}
|
|
} catch (RuntimeException ex) {
|
|
if (force) {
|
|
s_logger.info("Failed to expunge volume, but marking volume id=" + vol.getId() + " as expunged anyway " +
|
|
"due to force=true. Volume failed to expunge due to ", ex);
|
|
removeVolume = true;
|
|
} else {
|
|
throw ex;
|
|
}
|
|
} finally {
|
|
if (removeVolume) {
|
|
_volsDao.remove(vol.getId());
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Volume successfully expunged from " + poolId);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public void cleanupVolumes(long vmId) throws ConcurrentOperationException {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Cleaning storage for vm: " + vmId);
|
|
}
|
|
List<VolumeVO> volumesForVm = _volsDao.findByInstance(vmId);
|
|
List<VolumeVO> toBeExpunged = new ArrayList<VolumeVO>();
|
|
Transaction txn = Transaction.currentTxn();
|
|
txn.start();
|
|
for (VolumeVO vol : volumesForVm) {
|
|
if (vol.getVolumeType().equals(Type.ROOT)) {
|
|
// This check is for VM in Error state (volume is already destroyed)
|
|
if (!vol.getState().equals(Volume.State.Destroy)) {
|
|
destroyVolume(vol);
|
|
}
|
|
toBeExpunged.add(vol);
|
|
} else {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Detaching " + vol);
|
|
}
|
|
_volsDao.detachVolume(vol.getId());
|
|
}
|
|
}
|
|
txn.commit();
|
|
|
|
for (VolumeVO expunge : toBeExpunged) {
|
|
expungeVolume(expunge, false);
|
|
}
|
|
}
|
|
|
|
protected class StorageGarbageCollector implements Runnable {
|
|
|
|
public StorageGarbageCollector() {
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
s_logger.trace("Storage Garbage Collection Thread is running.");
|
|
|
|
cleanupStorage(true);
|
|
|
|
} catch (Exception e) {
|
|
s_logger.error("Caught the following Exception", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onManagementNodeJoined(List<ManagementServerHostVO> nodeList, long selfNodeId) {
|
|
// TODO Auto-generated method stub
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onManagementNodeLeft(List<ManagementServerHostVO> nodeList, long selfNodeId) {
|
|
for (ManagementServerHostVO vo : nodeList) {
|
|
if (vo.getMsid() == _serverId) {
|
|
s_logger.info("Cleaning up storage maintenance jobs associated with Management server" + vo.getMsid());
|
|
List<Long> poolIds = _storagePoolWorkDao.searchForPoolIdsForPendingWorkJobs(vo.getMsid());
|
|
if (poolIds.size() > 0) {
|
|
for (Long poolId : poolIds) {
|
|
StoragePoolVO pool = _storagePoolDao.findById(poolId);
|
|
// check if pool is in an inconsistent state
|
|
if (pool != null
|
|
&& (pool.getStatus().equals(StoragePoolStatus.ErrorInMaintenance) || pool.getStatus().equals(StoragePoolStatus.PrepareForMaintenance) || pool.getStatus().equals(
|
|
StoragePoolStatus.CancelMaintenance))) {
|
|
_storagePoolWorkDao.removePendingJobsOnMsRestart(vo.getMsid(), poolId);
|
|
pool.setStatus(StoragePoolStatus.ErrorInMaintenance);
|
|
_storagePoolDao.update(poolId, pool);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onManagementNodeIsolated() {
|
|
}
|
|
|
|
@Override
|
|
public CapacityVO getSecondaryStorageUsedStats(Long hostId, Long zoneId) {
|
|
SearchCriteria<HostVO> sc = _hostDao.createSearchCriteria();
|
|
if (zoneId != null) {
|
|
sc.addAnd("dataCenterId", SearchCriteria.Op.EQ, zoneId);
|
|
}
|
|
|
|
List<HostVO> hosts = new ArrayList<HostVO>();
|
|
if (hostId != null) {
|
|
hosts.add(ApiDBUtils.findHostById(hostId));
|
|
} else {
|
|
hosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(zoneId);
|
|
}
|
|
|
|
CapacityVO capacity = new CapacityVO(hostId, zoneId, null, null, 0, 0, CapacityVO.CAPACITY_TYPE_SECONDARY_STORAGE);
|
|
for (HostVO host : hosts) {
|
|
StorageStats stats = ApiDBUtils.getSecondaryStorageStatistics(host.getId());
|
|
if (stats == null) {
|
|
continue;
|
|
}
|
|
capacity.setUsedCapacity(stats.getByteUsed() + capacity.getUsedCapacity());
|
|
capacity.setTotalCapacity(stats.getCapacityBytes() + capacity.getTotalCapacity());
|
|
}
|
|
|
|
return capacity;
|
|
}
|
|
|
|
@Override
|
|
public CapacityVO getStoragePoolUsedStats(Long poolId, Long clusterId, Long podId, Long zoneId) {
|
|
SearchCriteria<StoragePoolVO> sc = _storagePoolDao.createSearchCriteria();
|
|
List<StoragePoolVO> pools = new ArrayList<StoragePoolVO>();
|
|
|
|
if (zoneId != null) {
|
|
sc.addAnd("dataCenterId", SearchCriteria.Op.EQ, zoneId);
|
|
}
|
|
|
|
if (podId != null) {
|
|
sc.addAnd("podId", SearchCriteria.Op.EQ, podId);
|
|
}
|
|
|
|
if (clusterId != null) {
|
|
sc.addAnd("clusterId", SearchCriteria.Op.EQ, clusterId);
|
|
}
|
|
|
|
if (poolId != null) {
|
|
sc.addAnd("hostOrPoolId", SearchCriteria.Op.EQ, poolId);
|
|
}
|
|
if (poolId != null) {
|
|
pools.add(_storagePoolDao.findById(poolId));
|
|
} else {
|
|
pools = _storagePoolDao.search(sc, null);
|
|
}
|
|
|
|
CapacityVO capacity = new CapacityVO(poolId, zoneId, podId, clusterId, 0, 0, CapacityVO.CAPACITY_TYPE_STORAGE);
|
|
for (StoragePoolVO storagePool : pools) {
|
|
StorageStats stats = ApiDBUtils.getStoragePoolStatistics(storagePool.getId());
|
|
if (stats == null) {
|
|
continue;
|
|
}
|
|
capacity.setUsedCapacity(stats.getByteUsed() + capacity.getUsedCapacity());
|
|
capacity.setTotalCapacity(stats.getCapacityBytes() + capacity.getTotalCapacity());
|
|
}
|
|
return capacity;
|
|
}
|
|
|
|
@Override
|
|
public StoragePool getStoragePool(long id) {
|
|
return _storagePoolDao.findById(id);
|
|
}
|
|
|
|
@Override
|
|
public VMTemplateHostVO findVmTemplateHost(long templateId, StoragePool pool) {
|
|
long dcId = pool.getDataCenterId();
|
|
Long podId = pool.getPodId();
|
|
|
|
List<HostVO> secHosts = _ssvmMgr.listSecondaryStorageHostsInOneZone(dcId);
|
|
|
|
// FIXME, for cloudzone, the local secondary storoge
|
|
if (pool.isLocal() && pool.getPoolType() == StoragePoolType.Filesystem && secHosts.isEmpty()) {
|
|
List<StoragePoolHostVO> sphs = _storagePoolHostDao.listByPoolId(pool.getId());
|
|
if (!sphs.isEmpty()) {
|
|
StoragePoolHostVO localStoragePoolHost = sphs.get(0);
|
|
return _templateHostDao.findLocalSecondaryStorageByHostTemplate(localStoragePoolHost.getHostId(), templateId);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (secHosts.size() == 1) {
|
|
VMTemplateHostVO templateHostVO = _templateHostDao.findByHostTemplate(secHosts.get(0).getId(), templateId);
|
|
return templateHostVO;
|
|
}
|
|
if (podId != null) {
|
|
List<VMTemplateHostVO> templHosts = _templateHostDao.listByTemplateStatus(templateId, dcId, podId, VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
|
|
if (templHosts != null && !templHosts.isEmpty()) {
|
|
Collections.shuffle(templHosts);
|
|
return templHosts.get(0);
|
|
}
|
|
}
|
|
List<VMTemplateHostVO> templHosts = _templateHostDao.listByTemplateStatus(templateId, dcId, VMTemplateStorageResourceAssoc.Status.DOWNLOADED);
|
|
if (templHosts != null && !templHosts.isEmpty()) {
|
|
Collections.shuffle(templHosts);
|
|
return templHosts.get(0);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public List<VMInstanceVO> listByStoragePool(long storagePoolId) {
|
|
SearchCriteria<VMInstanceVO> sc = StoragePoolSearch.create();
|
|
sc.setJoinParameters("vmVolume", "volumeType", Volume.Type.ROOT);
|
|
sc.setJoinParameters("vmVolume", "poolId", storagePoolId);
|
|
return _vmInstanceDao.search(sc, null);
|
|
}
|
|
|
|
@Override
|
|
@DB
|
|
public StoragePoolVO findLocalStorageOnHost(long hostId) {
|
|
SearchCriteria<StoragePoolVO> sc = LocalStorageSearch.create();
|
|
sc.setParameters("type", new Object[] { StoragePoolType.Filesystem, StoragePoolType.LVM });
|
|
sc.setJoinParameters("poolHost", "hostId", hostId);
|
|
List<StoragePoolVO> storagePools = _storagePoolDao.search(sc, null);
|
|
if (!storagePools.isEmpty()) {
|
|
return storagePools.get(0);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Host updateSecondaryStorage(long secStorageId, String newUrl) {
|
|
HostVO secHost = _hostDao.findById(secStorageId);
|
|
if (secHost == null) {
|
|
throw new InvalidParameterValueException("Can not find out the secondary storage id: " + secStorageId);
|
|
}
|
|
|
|
if (secHost.getType() != Host.Type.SecondaryStorage) {
|
|
throw new InvalidParameterValueException("host: " + secStorageId + " is not a secondary storage");
|
|
}
|
|
|
|
URI uri = null;
|
|
try {
|
|
uri = new URI(UriUtils.encodeURIComponent(newUrl));
|
|
if (uri.getScheme() == null) {
|
|
throw new InvalidParameterValueException("uri.scheme is null " + newUrl + ", add nfs:// as a prefix");
|
|
} else if (uri.getScheme().equalsIgnoreCase("nfs")) {
|
|
if (uri.getHost() == null || uri.getHost().equalsIgnoreCase("") || uri.getPath() == null || uri.getPath().equalsIgnoreCase("")) {
|
|
throw new InvalidParameterValueException("Your host and/or path is wrong. Make sure it's of the format nfs://hostname/path");
|
|
}
|
|
}
|
|
} catch (URISyntaxException e) {
|
|
throw new InvalidParameterValueException(newUrl + " is not a valid uri");
|
|
}
|
|
|
|
String oldUrl = secHost.getStorageUrl();
|
|
|
|
URI oldUri = null;
|
|
try {
|
|
oldUri = new URI(UriUtils.encodeURIComponent(oldUrl));
|
|
if (!oldUri.getScheme().equalsIgnoreCase(uri.getScheme())) {
|
|
throw new InvalidParameterValueException("can not change old scheme:" + oldUri.getScheme() + " to " + uri.getScheme());
|
|
}
|
|
} catch (URISyntaxException e) {
|
|
s_logger.debug("Failed to get uri from " + oldUrl);
|
|
}
|
|
|
|
secHost.setStorageUrl(newUrl);
|
|
secHost.setGuid(newUrl);
|
|
secHost.setName(newUrl);
|
|
_hostDao.update(secHost.getId(), secHost);
|
|
return secHost;
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
public String getSupportedImageFormatForCluster(Long clusterId) {
|
|
ClusterVO cluster = ApiDBUtils.findClusterById(clusterId);
|
|
|
|
if (cluster.getHypervisorType() == HypervisorType.XenServer) {
|
|
return "vhd";
|
|
} else if (cluster.getHypervisorType() == HypervisorType.KVM) {
|
|
return "qcow2";
|
|
} else if (cluster.getHypervisorType() == HypervisorType.VMware) {
|
|
return "ova";
|
|
} else if (cluster.getHypervisorType() == HypervisorType.Ovm) {
|
|
return "raw";
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public HypervisorType getHypervisorTypeFromFormat(ImageFormat format) {
|
|
|
|
if(format == null) {
|
|
return HypervisorType.None;
|
|
}
|
|
|
|
if (format == ImageFormat.VHD) {
|
|
return HypervisorType.XenServer;
|
|
} else if (format == ImageFormat.OVA) {
|
|
return HypervisorType.VMware;
|
|
} else if (format == ImageFormat.QCOW2) {
|
|
return HypervisorType.KVM;
|
|
} else if (format == ImageFormat.RAW) {
|
|
return HypervisorType.Ovm;
|
|
} else {
|
|
return HypervisorType.None;
|
|
}
|
|
}
|
|
|
|
private boolean checkUsagedSpace(StoragePool pool){
|
|
StatsCollector sc = StatsCollector.getInstance();
|
|
if (sc != null) {
|
|
long totalSize = pool.getCapacityBytes();
|
|
StorageStats stats = sc.getStoragePoolStats(pool.getId());
|
|
if(stats == null){
|
|
stats = sc.getStorageStats(pool.getId());
|
|
}
|
|
if (stats != null) {
|
|
double usedPercentage = ((double)stats.getByteUsed() / (double)totalSize);
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Checking pool " + pool.getId() + " for storage, totalSize: " + pool.getCapacityBytes() + ", usedBytes: " + stats.getByteUsed() + ", usedPct: " + usedPercentage + ", disable threshold: " + _storageUsedThreshold);
|
|
}
|
|
if (usedPercentage >= _storageUsedThreshold) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Insufficient space on pool: " + pool.getId() + " since its usage percentage: " +usedPercentage + " has crossed the pool.storage.capacity.disablethreshold: " + _storageUsedThreshold);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean storagePoolHasEnoughSpace(List<Volume> volumes, StoragePool pool) {
|
|
if(volumes == null || volumes.isEmpty())
|
|
return false;
|
|
|
|
if(!checkUsagedSpace(pool))
|
|
return false;
|
|
|
|
// allocated space includes template of specified volume
|
|
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
|
|
long allocatedSizeWithtemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, null);
|
|
long totalAskingSize = 0;
|
|
for (Volume volume : volumes) {
|
|
if(volume.getTemplateId()!=null){
|
|
VMTemplateVO tmpl = _templateDao.findById(volume.getTemplateId());
|
|
if (tmpl.getFormat() != ImageFormat.ISO){
|
|
allocatedSizeWithtemplate = _capacityMgr.getAllocatedPoolCapacity(poolVO, tmpl);
|
|
}
|
|
}
|
|
if(volume.getState() != Volume.State.Ready)
|
|
totalAskingSize = totalAskingSize + volume.getSize();
|
|
}
|
|
|
|
long totalOverProvCapacity;
|
|
if (pool.getPoolType() == StoragePoolType.NetworkFilesystem) {
|
|
totalOverProvCapacity = _storageOverprovisioningFactor.multiply(new BigDecimal(pool.getCapacityBytes())).longValue();// All this for the inaccuracy of floats for big number multiplication.
|
|
}else {
|
|
totalOverProvCapacity = pool.getCapacityBytes();
|
|
}
|
|
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Checking pool: " + pool.getId() + " for volume allocation " + volumes.toString() + ", maxSize : " + totalOverProvCapacity + ", totalAllocatedSize : " + allocatedSizeWithtemplate + ", askingSize : " + totalAskingSize + ", allocated disable threshold: " + _storageAllocatedThreshold);
|
|
}
|
|
|
|
double usedPercentage = (allocatedSizeWithtemplate + totalAskingSize) / (double)(totalOverProvCapacity);
|
|
if (usedPercentage > _storageAllocatedThreshold){
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Insufficient un-allocated capacity on: " + pool.getId() + " for volume allocation: " + volumes.toString() + " since its allocated percentage: " +usedPercentage + " has crossed the allocated pool.storage.allocated.capacity.disablethreshold: " + _storageAllocatedThreshold + ", skipping this pool");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (totalOverProvCapacity < (allocatedSizeWithtemplate + totalAskingSize)) {
|
|
if (s_logger.isDebugEnabled()) {
|
|
s_logger.debug("Insufficient un-allocated capacity on: " + pool.getId() + " for volume allocation: " + volumes.toString() + ", not enough storage, maxSize : " + totalOverProvCapacity + ", totalAllocatedSize : " + allocatedSizeWithtemplate + ", askingSize : " + totalAskingSize);
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
}
|