mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Merge branch '4.11'
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
		
						commit
						93e374599a
					
				| @ -19,6 +19,9 @@ package com.cloud.deploy; | ||||
| import com.cloud.deploy.DeploymentPlanner.ExcludeList; | ||||
| import com.cloud.vm.ReservationContext; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class DataCenterDeployment implements DeploymentPlan { | ||||
|     long _dcId; | ||||
|     Long _podId; | ||||
| @ -29,6 +32,7 @@ public class DataCenterDeployment implements DeploymentPlan { | ||||
|     ExcludeList _avoids = null; | ||||
|     boolean _recreateDisks; | ||||
|     ReservationContext _context; | ||||
|     List<Long> preferredHostIds = new ArrayList<>(); | ||||
| 
 | ||||
|     public DataCenterDeployment(long dataCenterId) { | ||||
|         this(dataCenterId, null, null, null, null, null); | ||||
| @ -93,4 +97,14 @@ public class DataCenterDeployment implements DeploymentPlan { | ||||
|         return _context; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setPreferredHosts(List<Long> hostIds) { | ||||
|         this.preferredHostIds = new ArrayList<>(hostIds); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<Long> getPreferredHosts() { | ||||
|         return this.preferredHostIds; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -19,6 +19,8 @@ package com.cloud.deploy; | ||||
| import com.cloud.deploy.DeploymentPlanner.ExcludeList; | ||||
| import com.cloud.vm.ReservationContext; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  */ | ||||
| public interface DeploymentPlan { | ||||
| @ -65,4 +67,8 @@ public interface DeploymentPlan { | ||||
|     Long getPhysicalNetworkId(); | ||||
| 
 | ||||
|     ReservationContext getReservationContext(); | ||||
| 
 | ||||
|     void setPreferredHosts(List<Long> hostIds); | ||||
| 
 | ||||
|     List<Long> getPreferredHosts(); | ||||
| } | ||||
|  | ||||
| @ -438,6 +438,11 @@ | ||||
|       <artifactId>cloud-plugin-host-anti-affinity</artifactId> | ||||
|       <version>${project.version}</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.apache.cloudstack</groupId> | ||||
|       <artifactId>cloud-plugin-host-affinity</artifactId> | ||||
|       <version>${project.version}</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|         <groupId>org.apache.cloudstack</groupId> | ||||
|         <artifactId>cloud-plugin-api-solidfire-intg-test</artifactId> | ||||
|  | ||||
| @ -248,7 +248,7 @@ | ||||
|         class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> | ||||
|         <property name="orderConfigKey" value="affinity.processors.order" /> | ||||
|         <property name="orderConfigDefault" | ||||
|             value="HostAntiAffinityProcessor,ExplicitDedicationProcessor" /> | ||||
|             value="HostAntiAffinityProcessor,ExplicitDedicationProcessor,HostAffinityProcessor" /> | ||||
|         <property name="excludeKey" value="affinity.processors.exclude" /> | ||||
|     </bean> | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										29
									
								
								plugins/affinity-group-processors/host-affinity/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								plugins/affinity-group-processors/host-affinity/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| <!-- | ||||
|   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. | ||||
| --> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <name>Apache CloudStack Plugin - Host Affinity Processor</name> | ||||
|     <artifactId>cloud-plugin-host-affinity</artifactId> | ||||
|     <parent> | ||||
|         <artifactId>cloudstack-plugins</artifactId> | ||||
|         <groupId>org.apache.cloudstack</groupId> | ||||
|         <version>4.12.0.0-SNAPSHOT</version> | ||||
|         <relativePath>../../pom.xml</relativePath> | ||||
|     </parent> | ||||
| </project> | ||||
| @ -0,0 +1,127 @@ | ||||
| // 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.affinity; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import java.util.HashSet; | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import com.cloud.vm.VMInstanceVO; | ||||
| import org.apache.commons.collections.CollectionUtils; | ||||
| import org.apache.log4j.Logger; | ||||
| 
 | ||||
| import org.apache.cloudstack.affinity.dao.AffinityGroupDao; | ||||
| import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; | ||||
| 
 | ||||
| import com.cloud.deploy.DeployDestination; | ||||
| import com.cloud.deploy.DeploymentPlan; | ||||
| import com.cloud.deploy.DeploymentPlanner.ExcludeList; | ||||
| import com.cloud.exception.AffinityConflictException; | ||||
| import com.cloud.vm.VirtualMachine; | ||||
| import com.cloud.vm.VirtualMachineProfile; | ||||
| import com.cloud.vm.dao.VMInstanceDao; | ||||
| 
 | ||||
| public class HostAffinityProcessor extends AffinityProcessorBase implements AffinityGroupProcessor { | ||||
| 
 | ||||
|     private static final Logger s_logger = Logger.getLogger(HostAffinityProcessor.class); | ||||
| 
 | ||||
|     @Inject | ||||
|     protected VMInstanceDao _vmInstanceDao; | ||||
|     @Inject | ||||
|     protected AffinityGroupDao _affinityGroupDao; | ||||
|     @Inject | ||||
|     protected AffinityGroupVMMapDao _affinityGroupVMMapDao; | ||||
| 
 | ||||
|     @Override | ||||
|     public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException { | ||||
|         VirtualMachine vm = vmProfile.getVirtualMachine(); | ||||
|         List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); | ||||
|         if (CollectionUtils.isNotEmpty(vmGroupMappings)) { | ||||
|             for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { | ||||
|                 processAffinityGroup(vmGroupMapping, plan, vm); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Process Affinity Group for VM deployment | ||||
|      */ | ||||
|     protected void processAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, DeploymentPlan plan, VirtualMachine vm) { | ||||
|         AffinityGroupVO group = _affinityGroupDao.findById(vmGroupMapping.getAffinityGroupId()); | ||||
|         s_logger.debug("Processing affinity group " + group.getName() + " for VM Id: " + vm.getId()); | ||||
| 
 | ||||
|         List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(group.getId()); | ||||
|         groupVMIds.remove(vm.getId()); | ||||
| 
 | ||||
|         List<Long> preferredHosts = getPreferredHostsFromGroupVMIds(groupVMIds); | ||||
|         plan.setPreferredHosts(preferredHosts); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get host ids set from vm ids list | ||||
|      */ | ||||
|     protected Set<Long> getHostIdSet(List<Long> vmIds) { | ||||
|         Set<Long> hostIds = new HashSet<>(); | ||||
|         for (Long groupVMId : vmIds) { | ||||
|             VMInstanceVO groupVM = _vmInstanceDao.findById(groupVMId); | ||||
|             hostIds.add(groupVM.getHostId()); | ||||
|         } | ||||
|         return hostIds; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get preferred host ids list from the affinity group VMs | ||||
|      */ | ||||
|     protected List<Long> getPreferredHostsFromGroupVMIds(List<Long> vmIds) { | ||||
|         return new ArrayList<>(getHostIdSet(vmIds)); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean check(VirtualMachineProfile vmProfile, DeployDestination plannedDestination) throws AffinityConflictException { | ||||
|         if (plannedDestination.getHost() == null) { | ||||
|             return true; | ||||
|         } | ||||
|         long plannedHostId = plannedDestination.getHost().getId(); | ||||
|         VirtualMachine vm = vmProfile.getVirtualMachine(); | ||||
|         List<AffinityGroupVMMapVO> vmGroupMappings = _affinityGroupVMMapDao.findByVmIdType(vm.getId(), getType()); | ||||
| 
 | ||||
|         if (CollectionUtils.isNotEmpty(vmGroupMappings)) { | ||||
|             for (AffinityGroupVMMapVO vmGroupMapping : vmGroupMappings) { | ||||
|                 if (!checkAffinityGroup(vmGroupMapping, vm, plannedHostId)) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check Affinity Group | ||||
|      */ | ||||
|     protected boolean checkAffinityGroup(AffinityGroupVMMapVO vmGroupMapping, VirtualMachine vm, long plannedHostId) { | ||||
|         List<Long> groupVMIds = _affinityGroupVMMapDao.listVmIdsByAffinityGroup(vmGroupMapping.getAffinityGroupId()); | ||||
|         groupVMIds.remove(vm.getId()); | ||||
| 
 | ||||
|         Set<Long> hostIds = getHostIdSet(groupVMIds); | ||||
|         return CollectionUtils.isEmpty(hostIds) || hostIds.contains(plannedHostId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| # 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. | ||||
| name=host-affinity | ||||
| parent=planner | ||||
| @ -0,0 +1,35 @@ | ||||
| <!-- | ||||
|   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. | ||||
| --> | ||||
| <beans xmlns="http://www.springframework.org/schema/beans" | ||||
|        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|        xmlns:context="http://www.springframework.org/schema/context" | ||||
|        xmlns:aop="http://www.springframework.org/schema/aop" | ||||
|        xsi:schemaLocation="http://www.springframework.org/schema/beans | ||||
|                       http://www.springframework.org/schema/beans/spring-beans.xsd | ||||
|                       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd | ||||
|                       http://www.springframework.org/schema/context | ||||
|                       http://www.springframework.org/schema/context/spring-context.xsd" | ||||
|     > | ||||
| 
 | ||||
|     <bean id="HostAffinityProcessor" | ||||
|           class="org.apache.cloudstack.affinity.HostAffinityProcessor"> | ||||
|         <property name="name" value="HostAffinityProcessor" /> | ||||
|         <property name="type" value="host affinity" /> | ||||
|     </bean> | ||||
| </beans> | ||||
| @ -0,0 +1,176 @@ | ||||
| // 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.affinity; | ||||
| 
 | ||||
| import com.cloud.deploy.DeployDestination; | ||||
| import com.cloud.deploy.DeploymentPlan; | ||||
| import com.cloud.host.Host; | ||||
| import com.cloud.vm.VMInstanceVO; | ||||
| import com.cloud.vm.VirtualMachine; | ||||
| import com.cloud.vm.VirtualMachineProfile; | ||||
| import com.cloud.vm.dao.VMInstanceDao; | ||||
| import org.apache.cloudstack.affinity.dao.AffinityGroupDao; | ||||
| import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| import org.junit.runners.JUnit4; | ||||
| import org.mockito.InjectMocks; | ||||
| import org.mockito.Mock; | ||||
| import org.mockito.MockitoAnnotations; | ||||
| import org.mockito.Spy; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.mockito.Matchers.any; | ||||
| import static org.mockito.Matchers.eq; | ||||
| import static org.mockito.Mockito.verify; | ||||
| import static org.mockito.Mockito.when; | ||||
| 
 | ||||
| @RunWith(JUnit4.class) | ||||
| public class HostAffinityProcessorTest { | ||||
| 
 | ||||
|     private static final long AFFINITY_GROUP_ID = 2L; | ||||
|     private static final String AFFINITY_GROUP_NAME = "Host affinity group"; | ||||
|     private static final Long VM_ID = 3L; | ||||
|     private static final Long GROUP_VM_1_ID = 1L; | ||||
|     private static final Long GROUP_VM_2_ID = 2L; | ||||
|     private static final Long HOST_ID = 1L; | ||||
|     private static final Long HOST_2_ID = 2L; | ||||
| 
 | ||||
|     @Mock | ||||
|     AffinityGroupDao affinityGroupDao; | ||||
| 
 | ||||
|     @Mock | ||||
|     AffinityGroupVMMapDao affinityGroupVMMapDao; | ||||
| 
 | ||||
|     @Mock | ||||
|     VMInstanceDao vmInstanceDao; | ||||
| 
 | ||||
|     @Spy | ||||
|     @InjectMocks | ||||
|     HostAffinityProcessor processor = new HostAffinityProcessor(); | ||||
| 
 | ||||
|     @Mock | ||||
|     DeploymentPlan plan; | ||||
| 
 | ||||
|     @Mock | ||||
|     VirtualMachine vm; | ||||
| 
 | ||||
|     @Mock | ||||
|     VMInstanceVO groupVM1; | ||||
| 
 | ||||
|     @Mock | ||||
|     VMInstanceVO groupVM2; | ||||
| 
 | ||||
|     @Mock | ||||
|     AffinityGroupVO affinityGroupVO; | ||||
| 
 | ||||
|     @Mock | ||||
|     AffinityGroupVMMapVO mapVO; | ||||
| 
 | ||||
|     @Mock | ||||
|     DeployDestination dest; | ||||
| 
 | ||||
|     @Mock | ||||
|     Host host; | ||||
| 
 | ||||
|     @Mock | ||||
|     VirtualMachineProfile profile; | ||||
| 
 | ||||
|     @Before | ||||
|     public void setup() { | ||||
|         MockitoAnnotations.initMocks(this); | ||||
| 
 | ||||
|         when(groupVM1.getHostId()).thenReturn(HOST_ID); | ||||
|         when(groupVM2.getHostId()).thenReturn(HOST_ID); | ||||
|         when(vmInstanceDao.findById(GROUP_VM_1_ID)).thenReturn(groupVM1); | ||||
|         when(vmInstanceDao.findById(GROUP_VM_2_ID)).thenReturn(groupVM2); | ||||
| 
 | ||||
|         when(affinityGroupVMMapDao.listVmIdsByAffinityGroup(AFFINITY_GROUP_ID)).thenReturn(new ArrayList<>(Arrays.asList(GROUP_VM_1_ID, GROUP_VM_2_ID, VM_ID))); | ||||
| 
 | ||||
|         when(vm.getId()).thenReturn(VM_ID); | ||||
| 
 | ||||
|         when(affinityGroupVO.getId()).thenReturn(AFFINITY_GROUP_ID); | ||||
|         when(affinityGroupVO.getName()).thenReturn(AFFINITY_GROUP_NAME); | ||||
|         when(mapVO.getAffinityGroupId()).thenReturn(AFFINITY_GROUP_ID); | ||||
| 
 | ||||
|         when(affinityGroupDao.findById(AFFINITY_GROUP_ID)).thenReturn(affinityGroupVO); | ||||
| 
 | ||||
|         when(dest.getHost()).thenReturn(host); | ||||
|         when(host.getId()).thenReturn(HOST_ID); | ||||
|         when(profile.getVirtualMachine()).thenReturn(vm); | ||||
|         when(affinityGroupVMMapDao.findByVmIdType(eq(VM_ID), any())).thenReturn(new ArrayList<>(Arrays.asList(mapVO))); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testProcessAffinityGroupMultipleVMs() { | ||||
|         processor.processAffinityGroup(mapVO, plan, vm); | ||||
|         verify(plan).setPreferredHosts(Arrays.asList(HOST_ID)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testProcessAffinityGroupEmptyGroup() { | ||||
|         when(affinityGroupVMMapDao.listVmIdsByAffinityGroup(AFFINITY_GROUP_ID)).thenReturn(new ArrayList<>()); | ||||
|         processor.processAffinityGroup(mapVO, plan, vm); | ||||
|         verify(plan).setPreferredHosts(new ArrayList<>()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetPreferredHostsFromGroupVMIdsMultipleVMs() { | ||||
|         List<Long> list = new ArrayList<>(Arrays.asList(GROUP_VM_1_ID, GROUP_VM_2_ID)); | ||||
|         List<Long> preferredHosts = processor.getPreferredHostsFromGroupVMIds(list); | ||||
|         assertNotNull(preferredHosts); | ||||
|         assertEquals(1, preferredHosts.size()); | ||||
|         assertEquals(HOST_ID, preferredHosts.get(0)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testGetPreferredHostsFromGroupVMIdsEmptyVMsList() { | ||||
|         List<Long> list = new ArrayList<>(); | ||||
|         List<Long> preferredHosts = processor.getPreferredHostsFromGroupVMIds(list); | ||||
|         assertNotNull(preferredHosts); | ||||
|         assertTrue(preferredHosts.isEmpty()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckAffinityGroup() { | ||||
|         assertTrue(processor.checkAffinityGroup(mapVO, vm, HOST_ID)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckAffinityGroupWrongHostId() { | ||||
|         assertFalse(processor.checkAffinityGroup(mapVO, vm, HOST_2_ID)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheck() { | ||||
|         assertTrue(processor.check(profile, dest)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckWrongHostId() { | ||||
|         when(host.getId()).thenReturn(HOST_2_ID); | ||||
|         assertFalse(processor.check(profile, dest)); | ||||
|     } | ||||
| } | ||||
| @ -45,6 +45,7 @@ import javax.xml.transform.stream.StreamResult; | ||||
| 
 | ||||
| import org.apache.commons.collections.MapUtils; | ||||
| import org.apache.commons.io.IOUtils; | ||||
| import org.apache.commons.lang.StringUtils; | ||||
| import org.apache.log4j.Logger; | ||||
| import org.libvirt.Connect; | ||||
| import org.libvirt.Domain; | ||||
| @ -332,9 +333,9 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo | ||||
|                     if ("disk".equals(deviceChildNode.getNodeName())) { | ||||
|                         Node diskNode = deviceChildNode; | ||||
| 
 | ||||
|                         String sourceFileDevText = getSourceFileDevText(diskNode); | ||||
|                         String sourceText = getSourceText(diskNode); | ||||
| 
 | ||||
|                         String path = getPathFromSourceFileDevText(migrateStorage.keySet(), sourceFileDevText); | ||||
|                         String path = getPathFromSourceText(migrateStorage.keySet(), sourceText); | ||||
| 
 | ||||
|                         if (path != null) { | ||||
|                             MigrateCommand.MigrateDiskInfo migrateDiskInfo = migrateStorage.remove(path); | ||||
| @ -383,10 +384,10 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo | ||||
|         return getXml(doc); | ||||
|     } | ||||
| 
 | ||||
|     private String getPathFromSourceFileDevText(Set<String> paths, String sourceFileDevText) { | ||||
|         if (paths != null && sourceFileDevText != null) { | ||||
|     private String getPathFromSourceText(Set<String> paths, String sourceText) { | ||||
|         if (paths != null && !StringUtils.isBlank(sourceText)) { | ||||
|             for (String path : paths) { | ||||
|                 if (sourceFileDevText.contains(path)) { | ||||
|                 if (sourceText.contains(path)) { | ||||
|                     return path; | ||||
|                 } | ||||
|             } | ||||
| @ -395,7 +396,7 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private String getSourceFileDevText(Node diskNode) { | ||||
|     private String getSourceText(Node diskNode) { | ||||
|         NodeList diskChildNodes = diskNode.getChildNodes(); | ||||
| 
 | ||||
|         for (int i = 0; i < diskChildNodes.getLength(); i++) { | ||||
| @ -415,6 +416,20 @@ public final class LibvirtMigrateCommandWrapper extends CommandWrapper<MigrateCo | ||||
|                 if (diskNodeAttribute != null) { | ||||
|                     return diskNodeAttribute.getTextContent(); | ||||
|                 } | ||||
| 
 | ||||
|                 diskNodeAttribute = diskNodeAttributes.getNamedItem("protocol"); | ||||
| 
 | ||||
|                 if (diskNodeAttribute != null) { | ||||
|                     String textContent = diskNodeAttribute.getTextContent(); | ||||
| 
 | ||||
|                     if ("rbd".equalsIgnoreCase(textContent)) { | ||||
|                         diskNodeAttribute = diskNodeAttributes.getNamedItem("name"); | ||||
| 
 | ||||
|                         if (diskNodeAttribute != null) { | ||||
|                             return diskNodeAttribute.getTextContent(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -51,6 +51,7 @@ | ||||
|     <module>api/discovery</module> | ||||
|     <module>acl/static-role-based</module> | ||||
|     <module>acl/dynamic-role-based</module> | ||||
|     <module>affinity-group-processors/host-affinity</module> | ||||
|     <module>affinity-group-processors/host-anti-affinity</module> | ||||
|     <module>affinity-group-processors/explicit-dedication</module> | ||||
|     <module>ca/root-ca</module> | ||||
|  | ||||
| @ -33,6 +33,7 @@ import javax.naming.ConfigurationException; | ||||
| import com.cloud.utils.db.Filter; | ||||
| import com.cloud.utils.fsm.StateMachine2; | ||||
| 
 | ||||
| import org.apache.commons.collections.CollectionUtils; | ||||
| import org.apache.log4j.Logger; | ||||
| import org.apache.cloudstack.affinity.AffinityGroupProcessor; | ||||
| import org.apache.cloudstack.affinity.AffinityGroupService; | ||||
| @ -321,7 +322,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|                     suitableHosts.add(host); | ||||
|                     Pair<Host, Map<Volume, StoragePool>> potentialResources = findPotentialDeploymentResources( | ||||
|                             suitableHosts, suitableVolumeStoragePools, avoids, | ||||
|                             getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes); | ||||
|                             getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes, plan.getPreferredHosts()); | ||||
|                     if (potentialResources != null) { | ||||
|                         pod = _podDao.findById(host.getPodId()); | ||||
|                         cluster = _clusterDao.findById(host.getClusterId()); | ||||
| @ -461,7 +462,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|                                 suitableHosts.add(host); | ||||
|                                 Pair<Host, Map<Volume, StoragePool>> potentialResources = findPotentialDeploymentResources( | ||||
|                                         suitableHosts, suitableVolumeStoragePools, avoids, | ||||
|                                         getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes); | ||||
|                                         getPlannerUsage(planner, vmProfile, plan, avoids), readyAndReusedVolumes, plan.getPreferredHosts()); | ||||
|                                 if (potentialResources != null) { | ||||
|                                     Map<Volume, StoragePool> storageVolMap = potentialResources.second(); | ||||
|                                     // remove the reused vol<->pool from | ||||
| @ -1077,7 +1078,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|                     // choose the potential host and pool for the VM | ||||
|                     if (!suitableVolumeStoragePools.isEmpty()) { | ||||
|                         Pair<Host, Map<Volume, StoragePool>> potentialResources = findPotentialDeploymentResources(suitableHosts, suitableVolumeStoragePools, avoid, | ||||
|                                 resourceUsageRequired, readyAndReusedVolumes); | ||||
|                                 resourceUsageRequired, readyAndReusedVolumes, plan.getPreferredHosts()); | ||||
| 
 | ||||
|                         if (potentialResources != null) { | ||||
|                             Host host = _hostDao.findById(potentialResources.first().getId()); | ||||
| @ -1217,11 +1218,12 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|     } | ||||
| 
 | ||||
|     protected Pair<Host, Map<Volume, StoragePool>> findPotentialDeploymentResources(List<Host> suitableHosts, Map<Volume, List<StoragePool>> suitableVolumeStoragePools, | ||||
|             ExcludeList avoid, DeploymentPlanner.PlannerResourceUsage resourceUsageRequired, List<Volume> readyAndReusedVolumes) { | ||||
|             ExcludeList avoid, DeploymentPlanner.PlannerResourceUsage resourceUsageRequired, List<Volume> readyAndReusedVolumes, List<Long> preferredHosts) { | ||||
|         s_logger.debug("Trying to find a potenial host and associated storage pools from the suitable host/pool lists for this VM"); | ||||
| 
 | ||||
|         boolean hostCanAccessPool = false; | ||||
|         boolean haveEnoughSpace = false; | ||||
|         boolean hostAffinityCheck = false; | ||||
| 
 | ||||
|         if (readyAndReusedVolumes == null) { | ||||
|             readyAndReusedVolumes = new ArrayList<Volume>(); | ||||
| @ -1245,6 +1247,7 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|                 s_logger.debug("Checking if host: " + potentialHost.getId() + " can access any suitable storage pool for volume: " + vol.getVolumeType()); | ||||
|                 List<StoragePool> volumePoolList = suitableVolumeStoragePools.get(vol); | ||||
|                 hostCanAccessPool = false; | ||||
|                 hostAffinityCheck = checkAffinity(potentialHost, preferredHosts); | ||||
|                 for (StoragePool potentialSPool : volumePoolList) { | ||||
|                     if (hostCanAccessSPool(potentialHost, potentialSPool)) { | ||||
|                         hostCanAccessPool = true; | ||||
| @ -1273,8 +1276,12 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|                     s_logger.warn("insufficient capacity to allocate all volumes"); | ||||
|                     break; | ||||
|                 } | ||||
|                 if (!hostAffinityCheck) { | ||||
|                     s_logger.debug("Host affinity check failed"); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             if (hostCanAccessPool && haveEnoughSpace && checkIfHostFitsPlannerUsage(potentialHost.getId(), resourceUsageRequired)) { | ||||
|             if (hostCanAccessPool && haveEnoughSpace && hostAffinityCheck && checkIfHostFitsPlannerUsage(potentialHost.getId(), resourceUsageRequired)) { | ||||
|                 s_logger.debug("Found a potential host " + "id: " + potentialHost.getId() + " name: " + potentialHost.getName() + | ||||
|                         " and associated storage pools for this VM"); | ||||
|                 return new Pair<Host, Map<Volume, StoragePool>>(potentialHost, storage); | ||||
| @ -1286,6 +1293,20 @@ StateListener<State, VirtualMachine.Event, VirtualMachine> { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * True if: | ||||
|      * - Affinity is not enabled (preferred host is empty) | ||||
|      * - Affinity is enabled and potential host is on the preferred hosts list | ||||
|      * | ||||
|      * False if not | ||||
|      */ | ||||
|     @DB | ||||
|     public boolean checkAffinity(Host potentialHost, List<Long> preferredHosts) { | ||||
|         boolean hostAffinityEnabled = CollectionUtils.isNotEmpty(preferredHosts); | ||||
|         boolean hostAffinityMatches = hostAffinityEnabled && preferredHosts.contains(potentialHost.getId()); | ||||
|         return !hostAffinityEnabled || hostAffinityMatches; | ||||
|     } | ||||
| 
 | ||||
|     protected boolean hostCanAccessSPool(Host host, StoragePool pool) { | ||||
|         boolean hostCanAccessSPool = false; | ||||
| 
 | ||||
|  | ||||
| @ -24,6 +24,7 @@ import java.util.Map; | ||||
| import javax.inject.Inject; | ||||
| 
 | ||||
| import org.apache.cloudstack.api.command.user.template.GetUploadParamsForTemplateCmd; | ||||
| import org.apache.commons.collections.CollectionUtils; | ||||
| import org.apache.log4j.Logger; | ||||
| 
 | ||||
| import org.apache.cloudstack.api.ApiConstants; | ||||
| @ -325,7 +326,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat | ||||
|         Long zoneId = cmd.getZoneId(); | ||||
|         // ignore passed zoneId if we are using region wide image store | ||||
|         List<ImageStoreVO> stores = _imgStoreDao.findRegionImageStores(); | ||||
|         if (!(stores != null && stores.size() > 0)) { | ||||
|         if (CollectionUtils.isEmpty(stores) && zoneId != null && zoneId > 0L) { | ||||
|             zoneList = new ArrayList<>(); | ||||
|             zoneList.add(zoneId); | ||||
|         } | ||||
|  | ||||
| @ -16,15 +16,19 @@ | ||||
| // under the License. | ||||
| package com.cloud.vm; | ||||
| 
 | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import javax.inject.Inject; | ||||
| import javax.naming.ConfigurationException; | ||||
| 
 | ||||
| import com.cloud.host.Host; | ||||
| import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao; | ||||
| import org.junit.Before; | ||||
| import org.junit.BeforeClass; | ||||
| @ -136,9 +140,12 @@ public class DeploymentPlanningManagerImplTest { | ||||
|     @Inject | ||||
|     UserVmDetailsDao vmDetailsDao; | ||||
| 
 | ||||
|     private static long domainId = 5L; | ||||
|     @Mock | ||||
|     Host host; | ||||
| 
 | ||||
|     private static long domainId = 5L; | ||||
|     private static long dataCenterId = 1L; | ||||
|     private static long hostId = 1l; | ||||
| 
 | ||||
|     @BeforeClass | ||||
|     public static void setUp() throws ConfigurationException { | ||||
| @ -172,6 +179,7 @@ public class DeploymentPlanningManagerImplTest { | ||||
|         planners.add(_planner); | ||||
|         _dpm.setPlanners(planners); | ||||
| 
 | ||||
|         Mockito.when(host.getId()).thenReturn(hostId); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
| @ -222,6 +230,26 @@ public class DeploymentPlanningManagerImplTest { | ||||
|         assertNull("Planner cannot handle, destination should be null! ", dest); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckAffinityEmptyPreferredHosts() { | ||||
|         assertTrue(_dpm.checkAffinity(host, new ArrayList<>())); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckAffinityNullPreferredHosts() { | ||||
|         assertTrue(_dpm.checkAffinity(host, null)); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckAffinityNotEmptyPreferredHostsContainingHost() { | ||||
|         assertTrue(_dpm.checkAffinity(host, Arrays.asList(3l, 4l, hostId, 2l))); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCheckAffinityNotEmptyPreferredHostsNotContainingHost() { | ||||
|         assertFalse(_dpm.checkAffinity(host, Arrays.asList(3l, 4l, 2l))); | ||||
|     } | ||||
| 
 | ||||
|     @Configuration | ||||
|     @ComponentScan(basePackageClasses = {DeploymentPlanningManagerImpl.class}, includeFilters = {@Filter(value = TestConfiguration.Library.class, | ||||
|                                                                                                          type = FilterType.CUSTOM)}, useDefaultFilters = false) | ||||
|  | ||||
| @ -21,8 +21,10 @@ from marvin.cloudstackTestCase import * | ||||
| from marvin.cloudstackAPI import * | ||||
| from marvin.lib.utils import * | ||||
| from marvin.lib.base import * | ||||
| from marvin.lib.common import * | ||||
| from marvin.sshClient import SshClient | ||||
| from marvin.lib.common import (get_domain, | ||||
|                                get_zone, | ||||
|                                get_template, | ||||
|                                list_virtual_machines) | ||||
| from nose.plugins.attrib import attr | ||||
| 
 | ||||
| class TestDeployVmWithAffinityGroup(cloudstackTestCase): | ||||
| @ -42,14 +44,14 @@ class TestDeployVmWithAffinityGroup(cloudstackTestCase): | ||||
|         cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) | ||||
|         cls.hypervisor = cls.testClient.getHypervisorInfo() | ||||
| 
 | ||||
|         cls.template = get_test_template( | ||||
|         cls.template = get_template( | ||||
|             cls.apiclient, | ||||
|             cls.zone.id, | ||||
|             cls.hypervisor | ||||
|         ) | ||||
|          | ||||
|         if cls.template == FAILED: | ||||
|             assert False, "get_test_template() failed to return template" | ||||
|             assert False, "get_template() failed to return template" | ||||
|              | ||||
|         cls.services["virtual_machine"]["zoneid"] = cls.zone.id | ||||
| 
 | ||||
| @ -69,6 +71,16 @@ class TestDeployVmWithAffinityGroup(cloudstackTestCase): | ||||
|         cls.ag = AffinityGroup.create(cls.apiclient, cls.services["virtual_machine"]["affinity"], | ||||
|             account=cls.account.name, domainid=cls.domain.id) | ||||
| 
 | ||||
|         host_affinity = { | ||||
|             "name": "marvin-host-affinity", | ||||
|             "type": "host affinity", | ||||
|         } | ||||
|         cls.affinity = AffinityGroup.create( | ||||
|             cls.apiclient, | ||||
|             host_affinity, | ||||
|             account=cls.account.name, | ||||
|             domainid=cls.domain.id | ||||
|         ) | ||||
|         cls._cleanup = [ | ||||
|             cls.service_offering, | ||||
|             cls.ag, | ||||
| @ -152,6 +164,81 @@ class TestDeployVmWithAffinityGroup(cloudstackTestCase): | ||||
|         self.assertNotEqual(host_of_vm1, host_of_vm2, | ||||
|             msg="Both VMs of affinity group %s are on the same host" % self.ag.name) | ||||
| 
 | ||||
|     @attr(tags=["basic", "advanced", "multihost"], required_hardware="false") | ||||
|     def test_DeployVmAffinityGroup(self): | ||||
|         """ | ||||
|         test DeployVM in affinity groups | ||||
| 
 | ||||
|         deploy VM1 and VM2 in the same host-affinity groups | ||||
|         Verify that the vms are deployed on the same host | ||||
|         """ | ||||
|         #deploy VM1 in affinity group created in setUp | ||||
|         vm1 = VirtualMachine.create( | ||||
|             self.apiclient, | ||||
|             self.services["virtual_machine"], | ||||
|             templateid=self.template.id, | ||||
|             accountid=self.account.name, | ||||
|             domainid=self.account.domainid, | ||||
|             serviceofferingid=self.service_offering.id, | ||||
|             affinitygroupnames=[self.affinity.name] | ||||
|         ) | ||||
| 
 | ||||
|         list_vm1 = list_virtual_machines( | ||||
|             self.apiclient, | ||||
|             id=vm1.id | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             isinstance(list_vm1, list), | ||||
|             True, | ||||
|             "Check list response returns a valid list" | ||||
|         ) | ||||
|         self.assertNotEqual( | ||||
|             len(list_vm1), | ||||
|             0, | ||||
|             "Check VM available in List Virtual Machines" | ||||
|         ) | ||||
|         vm1_response = list_vm1[0] | ||||
|         self.assertEqual( | ||||
|             vm1_response.state, | ||||
|             'Running', | ||||
|             msg="VM is not in Running state" | ||||
|         ) | ||||
|         host_of_vm1 = vm1_response.hostid | ||||
| 
 | ||||
|         #deploy VM2 in affinity group created in setUp | ||||
|         vm2 = VirtualMachine.create( | ||||
|             self.apiclient, | ||||
|             self.services["virtual_machine"], | ||||
|             templateid=self.template.id, | ||||
|             accountid=self.account.name, | ||||
|             domainid=self.account.domainid, | ||||
|             serviceofferingid=self.service_offering.id, | ||||
|             affinitygroupnames=[self.affinity.name] | ||||
|         ) | ||||
|         list_vm2 = list_virtual_machines( | ||||
|             self.apiclient, | ||||
|             id=vm2.id | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             isinstance(list_vm2, list), | ||||
|             True, | ||||
|             "Check list response returns a valid list" | ||||
|         ) | ||||
|         self.assertNotEqual( | ||||
|             len(list_vm2), | ||||
|             0, | ||||
|             "Check VM available in List Virtual Machines" | ||||
|         ) | ||||
|         vm2_response = list_vm2[0] | ||||
|         self.assertEqual( | ||||
|             vm2_response.state, | ||||
|             'Running', | ||||
|             msg="VM is not in Running state" | ||||
|         ) | ||||
|         host_of_vm2 = vm2_response.hostid | ||||
| 
 | ||||
|         self.assertEqual(host_of_vm1, host_of_vm2, | ||||
|             msg="Both VMs of affinity group %s are on different hosts" % self.affinity.name) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user