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