Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
dd4fdc6
Setup DRS for cluster with condensed algorithm
vishesh92 Jun 13, 2023
4aeb505
Add 'balanced' algorithm
vishesh92 Jun 20, 2023
33c421c
Add events for drs
vishesh92 Jul 3, 2023
abcd4cc
UI: Add execute drs button & events tab in cluster
vishesh92 Jul 3, 2023
9b8ea5e
Add unit tests for ClusterDrsServiceImpl
vishesh92 Jul 4, 2023
58d7235
Implement scheduled DRS at regular intervals
vishesh92 Jul 5, 2023
e1ea29e
Fix ClusterDrsServiceImplTest.java
vishesh92 Jul 6, 2023
c7d538d
Add smoke test
vishesh92 Jul 6, 2023
4895f6d
temp commit
vishesh92 Jul 7, 2023
7bcaa94
temp commit
vishesh92 Jul 11, 2023
5f57f11
temp commit
vishesh92 Jul 12, 2023
588ffed
temp commit
vishesh92 Jul 14, 2023
b5a73bd
temp commit
vishesh92 Jul 17, 2023
ac02d51
temp commit
vishesh92 Jul 18, 2023
4d6e731
temp commit
vishesh92 Jul 18, 2023
c561723
Add UI
vishesh92 Jul 19, 2023
ce5114f
Fix tests
vishesh92 Jul 19, 2023
3f9d042
Refactor
vishesh92 Jul 19, 2023
88d880f
Remove old drs records
vishesh92 Jul 19, 2023
88e39d9
add events in UI
vishesh92 Jul 20, 2023
e51140b
Add some unit tests
vishesh92 Jul 20, 2023
83bfa94
fixup
vishesh92 Jul 20, 2023
264b256
Ignore VMs not in running state
vishesh92 Jul 21, 2023
67612c9
consider only suitable hosts
vishesh92 Jul 21, 2023
b0ebbad
fix e2e test for cluster drs
vishesh92 Jul 21, 2023
2eb8ee0
Run startServer in tearDown for test_safe_shutdown to ensure manageme…
vishesh92 Jul 24, 2023
6071bab
refactor migration response
vishesh92 Jul 25, 2023
c839e44
Save generated plan
vishesh92 Jul 26, 2023
184adde
remove extra new line
vishesh92 Jul 26, 2023
6a89ef4
fix smoke test
vishesh92 Jul 26, 2023
beb2a3e
fix marvin method for executing drs
vishesh92 Jul 26, 2023
6960383
fixup
vishesh92 Jul 26, 2023
6fd85db
drs smoke test: wait for vm to come up
vishesh92 Jul 26, 2023
e3a9ae2
Wait for DRS execution to complete
vishesh92 Jul 27, 2023
1bb28a5
Add a wait for host's details to get updated
vishesh92 Jul 27, 2023
ffbd494
Wait until drs generates migrations
vishesh92 Jul 27, 2023
0049f8f
Run drs smoke test in ci
vishesh92 Jul 27, 2023
06c840b
Run test_cluster_drs ci test along with other tests
vishesh92 Jul 27, 2023
0eea44b
test_cluster_drs: Use large offering instead of small
vishesh92 Aug 7, 2023
0eeb6fd
Update as per feedback
vishesh92 Aug 25, 2023
9b4b937
Replace drs.iterations with drs.max.migrations
vishesh92 Aug 28, 2023
949446c
fixup
vishesh92 Aug 28, 2023
9d5d3ad
Rename drs.vm.migrations to drs.max.migrations
vishesh92 Aug 29, 2023
d0c882d
minor UI fixes
vishesh92 Sep 14, 2023
57aa7d3
Merge branch 'main' into cluster-drs
vishesh92 Sep 14, 2023
4044caa
Merge branch 'main' into cluster-drs
vishesh92 Oct 3, 2023
efc71a3
remove cost and benefit for now
vishesh92 Oct 3, 2023
ca22057
Replace powermock with mockito
vishesh92 Oct 3, 2023
480252c
Merge branch 'main' into cluster-drs
vishesh92 Oct 5, 2023
074ef44
Update text
vishesh92 Oct 5, 2023
af63133
Add loader on Generate DRS button
vishesh92 Oct 12, 2023
f92f20d
Merge branch 'main' into cluster-drs
vishesh92 Oct 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ jobs:
smoke/test_domain_network_offerings
smoke/test_domain_service_offerings
smoke/test_domain_vpc_offerings",
"smoke/test_dynamicroles
"smoke/test_cluster_drs
smoke/test_dynamicroles
smoke/test_enable_account_settings_for_domain
smoke/test_enable_role_based_users_in_projects
smoke/test_events_resource
Expand Down
5 changes: 5 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>${cs.commons-math3.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions api/src/main/java/com/cloud/event/EventTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,11 @@ public class EventTypes {
//Usage related events
public static final String EVENT_USAGE_REMOVE_USAGE_RECORDS = "USAGE.REMOVE.USAGE.RECORDS";

// DRS Events
public static final String EVENT_CLUSTER_DRS = "CLUSTER.DRS";
public static final String EVENT_CLUSTER_DRS_GENERATE = "CLUSTER.DRS.GENERATE";


// Netscaler Service Package events
public static final String EVENT_NETSCALER_SERVICEPACKAGE_ADD = "NETSCALER.SERVICEPACKAGE.ADD";
public static final String EVENT_NETSCALER_SERVICEPACKAGE_DELETE = "NETSCALER.SERVICEPACKAGE.DELETE";
Expand Down
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/server/ManagementService.java
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ public interface ManagementService {
*/
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(Long vmId, Long startIndex, Long pageSize, String keyword);

Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(VirtualMachine vm, Long startIndex, Long pageSize, String keyword, List<VirtualMachine> vmList);

/**
* List storage pools for live migrating of a volume. The API returns list of all pools in the cluster to which the
* volume can be migrated. Current pool is not included in the list. In case of vSphere datastore cluster storage pools,
Expand Down
3 changes: 3 additions & 0 deletions api/src/main/java/com/cloud/vm/VirtualMachine.java
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ public boolean isUsedBySystem() {
@Override
Long getHostId();


void setHostId(Long hostId);

/**
* @return should HA be enabled for this machine?
*/
Expand Down
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/vm/VmDetailConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public interface VmDetailConstants {

String CONFIG_DRIVE_LOCATION = "configDriveLocation";

String SKIP_DRS = "skipFromDRS";

// VM import with nic, disk and custom params for custom compute offering
String NIC = "nic";
String NETWORK = "network";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.exception.AffinityConflictException;
import com.cloud.utils.component.Adapter;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;

import java.util.List;

public interface AffinityGroupProcessor extends Adapter {

/**
Expand All @@ -35,7 +38,12 @@ public interface AffinityGroupProcessor extends Adapter {
* deployment plan that tells you where it's being deployed to.
* @param avoid
* avoid these data centers, pods, clusters, or hosts.
* @param vmList
* list of virtual machines objects according to which the affinity group should be processed.
* This can be used to process a theoretical state in some cases like generating DRS plans
*/
void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException;

void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,23 @@
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
import com.cloud.exception.AffinityConflictException;
import com.cloud.utils.component.AdapterBase;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;

import java.util.Collections;
import java.util.List;

public class AffinityProcessorBase extends AdapterBase implements AffinityGroupProcessor {

protected String _type;

@Override
public void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) throws AffinityConflictException {
process(vm, plan, avoid, Collections.emptyList());
}

@Override
public void process(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid, List<VirtualMachine> vmList) throws AffinityConflictException {

}

Expand Down
2 changes: 2 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ public class ApiConstants {
public static final String MIN_CPU_NUMBER = "mincpunumber";
public static final String MIN_MEMORY = "minmemory";
public static final String MIGRATION_TYPE = "migrationtype";
public static final String MIGRATIONS = "migrations";
public static final String MEMORY = "memory";
public static final String MODE = "mode";
public static final String NAME = "name";
Expand Down Expand Up @@ -661,6 +662,7 @@ public class ApiConstants {
public static final String SPECIFY_IP_RANGES = "specifyipranges";
public static final String IS_SOURCE_NAT = "issourcenat";
public static final String IS_STATIC_NAT = "isstaticnat";
public static final String ITERATIONS = "iterations";
public static final String SORT_BY = "sortby";
public static final String CHANGE_CIDR = "changecidr";
public static final String PURPOSE = "purpose";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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.api.command.admin.cluster;

import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.Host;
import com.cloud.user.Account;
import com.cloud.utils.UuidUtils;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.cluster.ClusterDrsService;
import org.apache.commons.collections.MapUtils;

import javax.inject.Inject;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@APICommand(name = "executeClusterDrsPlan",
description = "Execute DRS for a cluster. If there is another plan in progress for the same cluster, " +
"this command will fail.",
responseObject = ClusterDrsPlanResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false)
public class ExecuteClusterDrsPlanCmd extends BaseAsyncCmd {

@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ClusterResponse.class, required = true,
description = "ID of cluster")
private Long id;

@Parameter(
name = ApiConstants.MIGRATE_TO,
type = CommandType.MAP,
description = "Virtual Machine to destination host mapping. This parameter specifies the mapping between " +
"a vm and a host to migrate that VM. clusterid is required if this parameter is set." +
"Format of this parameter: migrateto[vm-index].vm=<uuid>&migrateto[vm-index].host=<uuid> " +
"Where, [vm-index] indicates the index to identify the vm that you want to migrate, " +
"vm=<uuid> indicates the UUID of the vm that you want to migrate, and " +
"host=<uuid> indicates the UUID of the host where you want to migrate the vm. " +
"Example: migrateto[0].vm=<71f43cd6-69b0-4d3b-9fbc-67f50963d60b>" +
"&migrateto[0].host=<a382f181-3d2b-4413-b92d-b8931befa7e1>" +
"&migrateto[1].vm=<88de0173-55c0-4c1c-a269-83d0279eeedf>" +
"&migrateto[1].host=<95d6e97c-6766-4d67-9a30-c449c15011d1>" +
"&migrateto[2].vm=<1b331390-59f2-4796-9993-bf11c6e76225>" +
"&migrateto[2].host=<41fdb564-9d3b-447d-88ed-7628f7640cbc>")
private Map<String, String> migrateVmTo;

@Inject
private ClusterDrsService clusterDrsService;

public Map<VirtualMachine, Host> getVmToHostMap() {
Map<VirtualMachine, Host> vmToHostMap = new HashMap<>();
if (MapUtils.isNotEmpty(migrateVmTo)) {
Collection<?> allValues = migrateVmTo.values();
Iterator<?> iter = allValues.iterator();
while (iter.hasNext()) {
HashMap<String, String> vmToHost = (HashMap<String, String>) iter.next();

String vmId = vmToHost.get("vm");
String hostId = vmToHost.get("host");

VirtualMachine vm;
Host host;
if (UuidUtils.isUuid(vmId)) {
vm = _entityMgr.findByUuid(VirtualMachine.class, vmId);
} else {
vm = _entityMgr.findById(VirtualMachine.class, Long.parseLong(vmId));
}

if (UuidUtils.isUuid(hostId)) {
host = _entityMgr.findByUuid(Host.class, hostId);
} else {
host = _entityMgr.findById(Host.class, Long.parseLong(hostId));
}

if (vm == null || host == null) {
throw new InvalidParameterValueException(
String.format("Unable to find the vm/host for vmId=%s, destHostId=%s", vmId, hostId));
}

vmToHostMap.put(vm, host);
}
}
return vmToHostMap;
}

@Override
public void execute() {
ClusterDrsPlanResponse response = clusterDrsService.executeDrsPlan(this);
response.setResponseName(getCommandName());
this.setResponseObject(response);
}

@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}

@Override
public Long getApiResourceId() {
return getId();
}

public Long getId() {
return id;
}

@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.Cluster;
}


@Override
public String getEventType() {
return EventTypes.EVENT_CLUSTER_DRS;
}

@Override
public String getEventDescription() {
return String.format("Executing DRS plan for cluster: %d", getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.api.command.admin.cluster;

import com.cloud.user.Account;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
import org.apache.cloudstack.cluster.ClusterDrsService;

import javax.inject.Inject;

import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMaxMigrations;

@APICommand(name = "generateClusterDrsPlan", description = "Generate DRS plan for a cluster",
responseObject = ClusterDrsPlanResponse.class, since = "4.19.0", requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false)
public class GenerateClusterDrsPlanCmd extends BaseCmd {

@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ClusterResponse.class, required = true,
description = "the ID of the Cluster")
private Long id;

@Parameter(name = ApiConstants.MIGRATIONS, type = CommandType.INTEGER,
description = "Maximum number of VMs to migrate for a DRS execution. Defaults to value of cluster's drs.vm.migrations setting")
private Integer migrations;

@Inject
private ClusterDrsService clusterDrsService;

public Integer getMaxMigrations() {
if (migrations == null) {
return ClusterDrsMaxMigrations.valueIn(getId());
}
return migrations;
}

public Long getId() {
return id;
}

@Override
public void execute() {
final ClusterDrsPlanResponse response = clusterDrsService.generateDrsPlan(this);
response.setResponseName(getCommandName());
response.setObjectName(getCommandName());
this.setResponseObject(response);
}

@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}

@Override
public Long getApiResourceId() {
return getId();
}

@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.Cluster;
}
}
Loading