| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.cloudstack.storage.endpoint; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; |
| import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; |
| import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; |
| import org.apache.cloudstack.engine.subsystem.api.storage.Scope; |
| import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.StorageAction; |
| import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; |
| import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; |
| import org.apache.cloudstack.storage.LocalHostEndpoint; |
| import org.apache.cloudstack.storage.RemoteHostEndPoint; |
| import org.apache.log4j.Logger; |
| import org.springframework.stereotype.Component; |
| |
| import com.cloud.capacity.CapacityManager; |
| 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; |
| import com.cloud.storage.DataStoreRole; |
| import com.cloud.storage.ScopeType; |
| import com.cloud.storage.Storage.TemplateType; |
| import com.cloud.utils.db.DB; |
| import com.cloud.utils.db.QueryBuilder; |
| import com.cloud.utils.db.SearchCriteria.Op; |
| import com.cloud.utils.db.TransactionLegacy; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.vm.VirtualMachine; |
| |
| @Component |
| public class DefaultEndPointSelector implements EndPointSelector { |
| private static final Logger s_logger = Logger.getLogger(DefaultEndPointSelector.class); |
| @Inject |
| private HostDao hostDao; |
| private final String findOneHostOnPrimaryStorage = "select t.id from " |
| + "(select h.id, cd.value " |
| + "from host h join storage_pool_host_ref s on h.id = s.host_id " |
| + "join cluster c on c.id=h.cluster_id " |
| + "left join cluster_details cd on c.id=cd.cluster_id and cd.name='" + CapacityManager.StorageOperationsExcludeCluster.key() + "' " |
| + "where h.status = 'Up' and h.type = 'Routing' and h.resource_state = 'Enabled' and s.pool_id = ? "; |
| |
| private String findOneHypervisorHostInScopeByType = "select h.id from host h where h.status = 'Up' and h.hypervisor_type = ? "; |
| private String findOneHypervisorHostInScope = "select h.id from host h where h.status = 'Up' and h.hypervisor_type is not null "; |
| |
| protected boolean moveBetweenPrimaryImage(DataStore srcStore, DataStore destStore) { |
| DataStoreRole srcRole = srcStore.getRole(); |
| DataStoreRole destRole = destStore.getRole(); |
| if ((srcRole == DataStoreRole.Primary && destRole.isImageStore()) || (srcRole.isImageStore() && destRole == DataStoreRole.Primary)) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| protected boolean moveBetweenCacheAndImage(DataStore srcStore, DataStore destStore) { |
| DataStoreRole srcRole = srcStore.getRole(); |
| DataStoreRole destRole = destStore.getRole(); |
| if (srcRole == DataStoreRole.Image && destRole == DataStoreRole.ImageCache || srcRole == DataStoreRole.ImageCache && destRole == DataStoreRole.Image) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| protected boolean moveBetweenImages(DataStore srcStore, DataStore destStore) { |
| DataStoreRole srcRole = srcStore.getRole(); |
| DataStoreRole destRole = destStore.getRole(); |
| if (srcRole == DataStoreRole.Image && destRole == DataStoreRole.Image) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| @DB |
| protected EndPoint findEndPointInScope(Scope scope, String sqlBase, Long poolId) { |
| StringBuilder sbuilder = new StringBuilder(); |
| sbuilder.append(sqlBase); |
| |
| if (scope != null) { |
| if (scope.getScopeType() == ScopeType.HOST) { |
| sbuilder.append(" and h.id = "); |
| sbuilder.append(scope.getScopeId()); |
| } else if (scope.getScopeType() == ScopeType.CLUSTER) { |
| sbuilder.append(" and h.cluster_id = "); |
| sbuilder.append(scope.getScopeId()); |
| } else if (scope.getScopeType() == ScopeType.ZONE) { |
| sbuilder.append(" and h.data_center_id = "); |
| sbuilder.append(scope.getScopeId()); |
| } |
| } |
| |
| // TODO: order by rand() is slow if there are lot of hosts |
| sbuilder.append(") t where t.value<>'true' or t.value is null"); //Added for exclude cluster's subquery |
| sbuilder.append(" ORDER by rand() limit 1"); |
| String sql = sbuilder.toString(); |
| HostVO host = null; |
| TransactionLegacy txn = TransactionLegacy.currentTxn(); |
| try (PreparedStatement pstmt = txn.prepareStatement(sql)) { |
| pstmt.setLong(1, poolId); |
| try(ResultSet rs = pstmt.executeQuery();) { |
| while (rs.next()) { |
| long id = rs.getLong(1); |
| host = hostDao.findById(id); |
| } |
| } catch (SQLException e) { |
| s_logger.warn("can't find endpoint", e); |
| } |
| } catch (SQLException e) { |
| s_logger.warn("can't find endpoint", e); |
| } |
| if (host == null) { |
| return null; |
| } |
| |
| return RemoteHostEndPoint.getHypervisorHostEndPoint(host); |
| } |
| |
| protected EndPoint findEndPointForImageMove(DataStore srcStore, DataStore destStore) { |
| // find any xenserver/kvm host in the scope |
| Scope srcScope = srcStore.getScope(); |
| Scope destScope = destStore.getScope(); |
| Scope selectedScope = null; |
| Long poolId = null; |
| |
| // assumption, at least one of scope should be zone, find the least |
| // scope |
| if (srcScope.getScopeType() != ScopeType.ZONE) { |
| selectedScope = srcScope; |
| poolId = srcStore.getId(); |
| } else if (destScope.getScopeType() != ScopeType.ZONE) { |
| selectedScope = destScope; |
| poolId = destStore.getId(); |
| } else { |
| // if both are zone scope |
| if (srcStore.getRole() == DataStoreRole.Primary) { |
| selectedScope = srcScope; |
| poolId = srcStore.getId(); |
| } else if (destStore.getRole() == DataStoreRole.Primary) { |
| selectedScope = destScope; |
| poolId = destStore.getId(); |
| } |
| } |
| return findEndPointInScope(selectedScope, findOneHostOnPrimaryStorage, poolId); |
| } |
| |
| @Override |
| public EndPoint select(DataObject srcData, DataObject destData) { |
| DataStore srcStore = srcData.getDataStore(); |
| DataStore destStore = destData.getDataStore(); |
| if (moveBetweenPrimaryImage(srcStore, destStore)) { |
| return findEndPointForImageMove(srcStore, destStore); |
| } else if (moveBetweenCacheAndImage(srcStore, destStore)) { |
| // pick ssvm based on image cache dc |
| DataStore selectedStore = null; |
| if (srcStore.getRole() == DataStoreRole.ImageCache) { |
| selectedStore = srcStore; |
| } else { |
| selectedStore = destStore; |
| } |
| EndPoint ep = findEndpointForImageStorage(selectedStore); |
| if (ep != null) { |
| return ep; |
| } |
| // handle special case where it is used in deploying ssvm for S3 |
| if (srcData instanceof TemplateInfo) { |
| TemplateInfo tmpl = (TemplateInfo)srcData; |
| if (tmpl.getTemplateType() == TemplateType.SYSTEM) { |
| ep = LocalHostEndpoint.getEndpoint(); |
| } |
| } |
| return ep; |
| } else if (moveBetweenImages(srcStore, destStore)) { |
| EndPoint ep = findEndpointForImageStorage(destStore); |
| return ep; |
| } |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| @Override |
| public EndPoint select(DataObject srcData, DataObject destData, StorageAction action) { |
| s_logger.error("IR24 select BACKUPSNAPSHOT from primary to secondary " + srcData.getId() + " dest=" + destData.getId()); |
| if (action == StorageAction.BACKUPSNAPSHOT && srcData.getDataStore().getRole() == DataStoreRole.Primary) { |
| SnapshotInfo srcSnapshot = (SnapshotInfo)srcData; |
| VolumeInfo volumeInfo = srcSnapshot.getBaseVolume(); |
| VirtualMachine vm = volumeInfo.getAttachedVM(); |
| if (srcSnapshot.getHypervisorType() == Hypervisor.HypervisorType.KVM) { |
| if (vm != null && vm.getState() == VirtualMachine.State.Running) { |
| return getEndPointFromHostId(vm.getHostId()); |
| } |
| } |
| if (srcSnapshot.getHypervisorType() == Hypervisor.HypervisorType.VMware) { |
| if (vm != null) { |
| Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); |
| if (hostId != null) { |
| return getEndPointFromHostId(hostId); |
| } |
| } |
| } |
| } |
| return select(srcData, destData); |
| } |
| |
| protected EndPoint findEndpointForPrimaryStorage(DataStore store) { |
| return findEndPointInScope(store.getScope(), findOneHostOnPrimaryStorage, store.getId()); |
| } |
| |
| protected EndPoint findEndpointForImageStorage(DataStore store) { |
| Long dcId = null; |
| Scope storeScope = store.getScope(); |
| if (storeScope.getScopeType() == ScopeType.ZONE) { |
| dcId = storeScope.getScopeId(); |
| } |
| // find ssvm that can be used to download data to store. For zone-wide |
| // image store, use SSVM for that zone. For region-wide store, |
| // we can arbitrarily pick one ssvm to do that task |
| List<HostVO> ssAHosts = listUpAndConnectingSecondaryStorageVmHost(dcId); |
| if (ssAHosts == null || ssAHosts.isEmpty()) { |
| return null; |
| } |
| Collections.shuffle(ssAHosts); |
| HostVO host = ssAHosts.get(0); |
| return RemoteHostEndPoint.getHypervisorHostEndPoint(host); |
| } |
| |
| private List<HostVO> listUpAndConnectingSecondaryStorageVmHost(Long dcId) { |
| QueryBuilder<HostVO> sc = QueryBuilder.create(HostVO.class); |
| if (dcId != null) { |
| sc.and(sc.entity().getDataCenterId(), Op.EQ, dcId); |
| } |
| sc.and(sc.entity().getStatus(), Op.IN, Status.Up, Status.Connecting); |
| sc.and(sc.entity().getType(), Op.EQ, Host.Type.SecondaryStorageVM); |
| return sc.list(); |
| } |
| |
| @Override |
| public EndPoint select(DataObject object) { |
| DataStore store = object.getDataStore(); |
| EndPoint ep = select(store); |
| if (ep != null) { |
| return ep; |
| } |
| if (object instanceof TemplateInfo) { |
| TemplateInfo tmplInfo = (TemplateInfo)object; |
| if (store.getScope().getScopeType() == ScopeType.ZONE && store.getScope().getScopeId() == null && tmplInfo.getTemplateType() == TemplateType.SYSTEM) { |
| return LocalHostEndpoint.getEndpoint(); // for bootstrap system vm template downloading to region image store |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public EndPoint select(DataStore store, String downloadUrl){ |
| |
| HostVO host = null; |
| try { |
| URI uri = new URI(downloadUrl); |
| String scheme = uri.getScheme(); |
| String publicIp = uri.getHost(); |
| // If its https then public ip will be of the form xxx-xxx-xxx-xxx.mydomain.com |
| if(scheme.equalsIgnoreCase("https")){ |
| publicIp = publicIp.split("\\.")[0]; // We want xxx-xxx-xxx-xxx |
| publicIp = publicIp.replace("-","."); // We not want the IP - xxx.xxx.xxx.xxx |
| } |
| host = hostDao.findByPublicIp(publicIp); |
| if(host != null){ |
| return RemoteHostEndPoint.getHypervisorHostEndPoint(host); |
| } |
| |
| } catch (URISyntaxException e) { |
| s_logger.debug("Received URISyntaxException for url" +downloadUrl); |
| } |
| |
| // If ssvm doesnt exist then find any ssvm in the zone. |
| s_logger.debug("Coudn't find ssvm for url" +downloadUrl); |
| return findEndpointForImageStorage(store); |
| } |
| |
| @Override |
| public EndPoint select(DataStore store) { |
| if (store.getRole() == DataStoreRole.Primary) { |
| return findEndpointForPrimaryStorage(store); |
| } else if (store.getRole() == DataStoreRole.Image || store.getRole() == DataStoreRole.ImageCache) { |
| // in case there is no ssvm, directly send down command hypervisor |
| // host |
| // otherwise, send to localhost for bootstrap system vm template |
| // download |
| return findEndpointForImageStorage(store); |
| } else { |
| throw new CloudRuntimeException("not implemented yet"); |
| } |
| } |
| |
| private EndPoint getEndPointFromHostId(Long hostId) { |
| HostVO host = hostDao.findById(hostId); |
| return RemoteHostEndPoint.getHypervisorHostEndPoint(host); |
| } |
| |
| @Override |
| public EndPoint select(DataObject object, StorageAction action) { |
| if (action == StorageAction.TAKESNAPSHOT) { |
| SnapshotInfo snapshotInfo = (SnapshotInfo)object; |
| if (snapshotInfo.getHypervisorType() == Hypervisor.HypervisorType.KVM) { |
| VolumeInfo volumeInfo = snapshotInfo.getBaseVolume(); |
| VirtualMachine vm = volumeInfo.getAttachedVM(); |
| if ((vm != null) && (vm.getState() == VirtualMachine.State.Running)) { |
| Long hostId = vm.getHostId(); |
| return getEndPointFromHostId(hostId); |
| } |
| } |
| } else if (action == StorageAction.MIGRATEVOLUME) { |
| VolumeInfo volume = (VolumeInfo)object; |
| if (volume.getHypervisorType() == Hypervisor.HypervisorType.Hyperv || volume.getHypervisorType() == Hypervisor.HypervisorType.VMware) { |
| VirtualMachine vm = volume.getAttachedVM(); |
| if ((vm != null) && (vm.getState() == VirtualMachine.State.Running)) { |
| Long hostId = vm.getHostId(); |
| return getEndPointFromHostId(hostId); |
| } |
| } |
| } else if (action == StorageAction.DELETEVOLUME) { |
| VolumeInfo volume = (VolumeInfo)object; |
| if (volume.getHypervisorType() == Hypervisor.HypervisorType.VMware) { |
| VirtualMachine vm = volume.getAttachedVM(); |
| if (vm != null) { |
| Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); |
| if (hostId != null) { |
| return getEndPointFromHostId(hostId); |
| } |
| } |
| } |
| } |
| return select(object); |
| } |
| |
| @Override |
| public EndPoint select(Scope scope, Long storeId) { |
| return findEndPointInScope(scope, findOneHostOnPrimaryStorage, storeId); |
| } |
| |
| @Override |
| public List<EndPoint> selectAll(DataStore store) { |
| List<EndPoint> endPoints = new ArrayList<EndPoint>(); |
| if (store.getScope().getScopeType() == ScopeType.HOST) { |
| HostVO host = hostDao.findById(store.getScope().getScopeId()); |
| |
| endPoints.add(RemoteHostEndPoint.getHypervisorHostEndPoint(host)); |
| } else if (store.getScope().getScopeType() == ScopeType.CLUSTER) { |
| QueryBuilder<HostVO> sc = QueryBuilder.create(HostVO.class); |
| sc.and(sc.entity().getClusterId(), Op.EQ, store.getScope().getScopeId()); |
| sc.and(sc.entity().getStatus(), Op.EQ, Status.Up); |
| List<HostVO> hosts = sc.list(); |
| for (HostVO host : hosts) { |
| endPoints.add(RemoteHostEndPoint.getHypervisorHostEndPoint(host)); |
| } |
| |
| } else { |
| throw new CloudRuntimeException("shouldn't use it for other scope"); |
| } |
| return endPoints; |
| } |
| } |