Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
// under the License.
package com.cloud.network.as;

import com.cloud.user.Account;
import org.apache.cloudstack.framework.config.ConfigKey;

import com.cloud.user.Account;

public interface AutoScaleManager extends AutoScaleService {

ConfigKey<Integer> AutoScaleStatsInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class,
Expand Down Expand Up @@ -63,7 +64,5 @@ public interface AutoScaleManager extends AutoScaleService {

void removeVmFromVmGroup(Long vmId);

String getNextVmHostName(AutoScaleVmGroupVO asGroup);

void checkAutoScaleVmGroupName(String groupName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@

import javax.inject.Inject;

import com.cloud.network.NetworkModel;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
Expand Down Expand Up @@ -113,6 +112,7 @@
import com.cloud.network.Network;
import com.cloud.network.Network.Capability;
import com.cloud.network.Network.IpAddresses;
import com.cloud.network.NetworkModel;
import com.cloud.network.as.AutoScaleCounter.AutoScaleCounterParam;
import com.cloud.network.as.dao.AutoScalePolicyConditionMapDao;
import com.cloud.network.as.dao.AutoScalePolicyDao;
Expand Down Expand Up @@ -146,7 +146,9 @@
import com.cloud.server.ResourceTag;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.template.TemplateManager;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account;
Expand Down Expand Up @@ -280,6 +282,8 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
private NetworkOfferingDao networkOfferingDao;
@Inject
private VirtualMachineManager virtualMachineManager;
@Inject
GuestOSDao guestOSDao;

private static final String PARAM_ROOT_DISK_SIZE = "rootdisksize";
private static final String PARAM_DISK_OFFERING_ID = "diskofferingid";
Expand Down Expand Up @@ -1810,26 +1814,28 @@ protected UserVm createNewVM(AutoScaleVmGroupVO asGroup) {
List<Long> affinityGroupIdList = getVmAffinityGroupId(deployParams);
updateVmDetails(deployParams, customParameters);

String vmHostName = getNextVmHostName(asGroup);
Pair<String, String> vmHostAndDisplayName = getNextVmHostAndDisplayName(asGroup, template);
String vmHostName = vmHostAndDisplayName.first();
String vmDisplayName = vmHostAndDisplayName.second();
asGroup.setNextVmSeq(asGroup.getNextVmSeq() + 1);
autoScaleVmGroupDao.persist(asGroup);

if (zone.getNetworkType() == NetworkType.Basic) {
vm = userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, null, owner, vmHostName,
vmHostName, diskOfferingId, dataDiskSize, null, null,
vmDisplayName, diskOfferingId, dataDiskSize, null, null,
hypervisorType, HTTPMethod.GET, userData, userDataId, userDataDetails, sshKeyPairs,
null, null, true, null, affinityGroupIdList, customParameters, null, null, null,
null, true, overrideDiskOfferingId, null, null);
} else {
if (networkModel.checkSecurityGroupSupportForNetwork(owner, zone, networkIds,
Collections.emptyList())) {
vm = userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, null,
owner, vmHostName,vmHostName, diskOfferingId, dataDiskSize, null, null,
owner, vmHostName, vmDisplayName, diskOfferingId, dataDiskSize, null, null,
hypervisorType, HTTPMethod.GET, userData, userDataId, userDataDetails, sshKeyPairs,
null, null, true, null, affinityGroupIdList, customParameters, null, null, null,
null, true, overrideDiskOfferingId, null, null, null);
} else {
vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, vmHostName, vmHostName,
vm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, vmHostName, vmDisplayName,
diskOfferingId, dataDiskSize, null, null,
hypervisorType, HTTPMethod.GET, userData, userDataId, userDataDetails, sshKeyPairs,
null, addrs, true, null, affinityGroupIdList, customParameters, null, null, null,
Expand Down Expand Up @@ -1954,13 +1960,37 @@ public void updateVmDetails(Map<String, String> deployParams, Map<String, String
}
}

@Override
public String getNextVmHostName(AutoScaleVmGroupVO asGroup) {
protected String getTrimmedHostNameForWindows(String name) {
String valid = name.replaceAll("[^a-zA-Z0-9-]", "");
String hostName = valid.length() > 15 ? valid.substring(valid.length() - 15) : valid;
if (!hostName.isEmpty() && !Character.isLetter(hostName.charAt(0))) {
for (int i = 0; i < hostName.length(); i++) {
if (Character.isLetter(hostName.charAt(i))) {
hostName = hostName.charAt(i) + hostName.substring(i + 1);
break;
}
}
if (!Character.isLetter(hostName.charAt(0))) {
hostName = hostName.length() < 15 ? "a" + hostName : "a" + hostName.substring(1);
}
}
return hostName;
}

protected Pair<String, String> getNextVmHostAndDisplayName(AutoScaleVmGroupVO asGroup, VirtualMachineTemplate template) {
template.getGuestOSId();
GuestOSVO guestOSVO = guestOSDao.findById(template.getGuestOSId());
String osName = guestOSVO != null ? StringUtils.firstNonBlank(guestOSVO.getName(), guestOSVO.getDisplayName()) : "";
boolean isWindows = osName.toLowerCase().contains("windows");
String vmHostNameSuffix = "-" + asGroup.getNextVmSeq() + "-" +
RandomStringUtils.random(VM_HOSTNAME_RANDOM_SUFFIX_LENGTH, 0, 0, true, false, (char[])null, new SecureRandom()).toLowerCase();
// Truncate vm group name because max length of vm name is 63
int subStringLength = Math.min(asGroup.getName().length(), 63 - VM_HOSTNAME_PREFIX.length() - vmHostNameSuffix.length());
return VM_HOSTNAME_PREFIX + asGroup.getName().substring(0, subStringLength) + vmHostNameSuffix;
String name = VM_HOSTNAME_PREFIX + asGroup.getName().substring(0, subStringLength) + vmHostNameSuffix;
if (!isWindows || name.length() <= 15) {
return new Pair<>(name, name);
}
return new Pair<>(getTrimmedHostNameForWindows(name), name);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOSVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
Expand Down Expand Up @@ -259,9 +261,10 @@ public class AutoScaleManagerImplTest {
LoadBalancingRulesService loadBalancingRulesService;
@Mock
VMInstanceDao vmInstanceDao;

@Mock
VirtualMachineManager virtualMachineManager;
@Mock
GuestOSDao guestOSDao;

AccountVO account;
UserVO user;
Expand Down Expand Up @@ -420,6 +423,11 @@ public void setUp() {
userDataDetails.put("0", new HashMap<>() {{ put("key1", "value1"); put("key2", "value2"); }});
Mockito.doReturn(userDataFinal).when(userVmMgr).finalizeUserData(any(), any(), any());
Mockito.doReturn(userDataFinal).when(userDataMgr).validateUserData(eq(userDataFinal), nullable(BaseCmd.HTTPMethod.class));

when(templateMock.getGuestOSId()).thenReturn(100L);
GuestOSVO guestOSMock = Mockito.mock(GuestOSVO.class);
when(guestOSDao.findById(anyLong())).thenReturn(guestOSMock);
when(guestOSMock.getName()).thenReturn("linux");
}

@After
Expand Down Expand Up @@ -2495,4 +2503,115 @@ public void destroyVm() {

Mockito.verify(userVmMgr).expunge(eq(userVmMock));
}

@Test
public void getNextVmHostAndDisplayNameGeneratesCorrectHostAndDisplayNameForLinuxTemplate() {
when(asVmGroupMock.getName()).thenReturn(vmGroupName);
when(asVmGroupMock.getNextVmSeq()).thenReturn(1L);
Pair<String, String> result = autoScaleManagerImplSpy.getNextVmHostAndDisplayName(asVmGroupMock, templateMock);
String vmHostNamePattern = AutoScaleManagerImpl.VM_HOSTNAME_PREFIX + vmGroupName +
"-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}";
Assert.assertTrue(result.first().matches(vmHostNamePattern));
Assert.assertEquals(result.first(), result.second());
}

private void runGetNextVmHostAndDisplayNameGeneratesCorrectHostAndDisplayNameForWindowsTemplate() {
when(asVmGroupMock.getName()).thenReturn(vmGroupName);
when(asVmGroupMock.getNextVmSeq()).thenReturn(1L);
when(templateMock.getGuestOSId()).thenReturn(1L);
Pair<String, String> result = autoScaleManagerImplSpy.getNextVmHostAndDisplayName(asVmGroupMock, templateMock);
String vmHostNamePattern = AutoScaleManagerImpl.VM_HOSTNAME_PREFIX + vmGroupName +
"-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}";
Assert.assertTrue(result.second().matches(vmHostNamePattern));
Assert.assertEquals(15, result.first().length());
Assert.assertTrue(result.second().endsWith(result.first()));

}

@Test
public void getNextVmHostAndDisplayNameGeneratesCorrectHostAndDisplayNameForWindowsTemplateUsingGuestOsName() {
GuestOSVO guestOS = Mockito.mock(GuestOSVO.class);
when(guestOS.getName()).thenReturn("Windows Server");
when(guestOSDao.findById(1L)).thenReturn(guestOS);
runGetNextVmHostAndDisplayNameGeneratesCorrectHostAndDisplayNameForWindowsTemplate();
}

@Test
public void getNextVmHostAndDisplayNameGeneratesCorrectHostAndDisplayNameForWindowsTemplateUsingGuestOsDisplayName() {
GuestOSVO guestOS = Mockito.mock(GuestOSVO.class);
when(guestOS.getDisplayName()).thenReturn("Windows Server");
when(guestOSDao.findById(1L)).thenReturn(guestOS);
runGetNextVmHostAndDisplayNameGeneratesCorrectHostAndDisplayNameForWindowsTemplate();
}

@Test
public void getNextVmHostAndDisplayNameTruncatesGroupNameWhenExceedingMaxLength() {
when(asVmGroupMock.getName()).thenReturn(vmGroupNameWithMaxLength);
when(asVmGroupMock.getNextVmSeq()).thenReturn(1L);
Pair<String, String> result = autoScaleManagerImplSpy.getNextVmHostAndDisplayName(asVmGroupMock, templateMock);
Assert.assertTrue(result.first().length() <= 63);
Assert.assertTrue(result.second().length() <= 63);
}

@Test
public void getNextVmHostAndDisplayNameHandlesNullGuestOS() {
when(asVmGroupMock.getName()).thenReturn(vmGroupName);
when(asVmGroupMock.getNextVmSeq()).thenReturn(1L);
when(templateMock.getGuestOSId()).thenReturn(1L);
when(guestOSDao.findById(1L)).thenReturn(null);
Pair<String, String> result = autoScaleManagerImplSpy.getNextVmHostAndDisplayName(asVmGroupMock, templateMock);
String vmHostNamePattern = AutoScaleManagerImpl.VM_HOSTNAME_PREFIX + vmGroupName +
"-" + asVmGroupMock.getNextVmSeq() + "-[a-z]{6}";
Assert.assertTrue(result.first().matches(vmHostNamePattern));
Assert.assertEquals(result.first(), result.second());
}

@Test
public void trimsValidWindowsHostName() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("ValidVMName1234");
Assert.assertEquals("ValidVMName1234", result);
}

@Test
public void trimsInvalidCharactersFromHostName() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("Invalid@Host#Name!");
Assert.assertEquals("InvalidHostName", result);
}

@Test
public void ensuresHostNameStartsWithLetter() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("123456789012345");
Assert.assertTrue(Character.isLetter(result.charAt(0)));
}

@Test
public void prefixesWithLetterIfNoLetterExists() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("1234567890-12345");
Assert.assertEquals("a34567890-12345", result);
}

@Test
public void trimsHostNameToLast15Characters() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("ThisIsAReallyLongHost-Name_1234");
Assert.assertEquals("ngHost-Name1234", result);
}

@Test
public void handlesEmptyHostName() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("");
Assert.assertEquals("", result);
}

@Test
public void handlesHostNameWithOnlyMostlyInvalidCharacters() {
AutoScaleManagerImpl manager = new AutoScaleManagerImpl();
String result = manager.getTrimmedHostNameForWindows("@#$%^&*() 1");
Assert.assertEquals("a1", result);
}
}
Loading