Skip to content

Commit

Permalink
feature: Support Multi-arch Zones (#9619)
Browse files Browse the repository at this point in the history
This introduces the multi-arch zones, allowing users to select the VM arch upon deployment. 

Multi-arch zone support in CloudStack can allow admins to mix x86_64 & arm64 hosts within the same zone with the following changes proposed:
- All hosts in a clusters need to be homogenous, wrt host CPU type (amd64 vs arm64) and hypevisor
- Arch-aware templates & ISOs:
   -  Add support for a new arch field (default set of: amd64 and arm64), when unspecified defaults to amd64 and for existing templates & iso
   -  Allow admins to edit the arch type of the registered template & iso
- Arch-aware clusters and host:
   - Add new attribute field for cluster and hosts (kvm host agents can automatically report this, arch of the first host of the cluster is cluster's architecture), defaults to amd64 when not specified
   - Allow admins to edit the arch of an existing cluster
- VM deployment form (UI):
   - In a multi-arch zone/env, the VM deployment form can allow some kind of template/iso filtration in the UI
   - Users should be able to select arch: amd64 & arm64; but this is shown only in a multi-arch zone (env)
- VM orchestration and lifecycle operations:
   - Use of VM/template's arch to correctly decide where to provision the VM (on the correct strictly arch-matching host/clusters) & other lifecycle operations (such as migration from/to arch-matching hosts)

Co-authored-by: Rohit Yadav <rohit.yadav@shapeblue.com>
  • Loading branch information
nvazquez and rohityadavcloud authored Sep 6, 2024
1 parent 52b0696 commit 8c8d115
Show file tree
Hide file tree
Showing 72 changed files with 926 additions and 47 deletions.
17 changes: 17 additions & 0 deletions agent/src/main/java/com/cloud/agent/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,13 @@ protected void setupStartupCommand(final StartupCommand startup) {
startup.setGuid(getResourceGuid());
startup.setResourceName(getResourceName());
startup.setVersion(getVersion());
startup.setArch(getAgentArch());
}

protected String getAgentArch() {
final Script command = new Script("/usr/bin/arch", 500, logger);
final OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
return command.execute(parser);
}

@Override
Expand Down Expand Up @@ -858,11 +865,21 @@ public void processReadyCommand(final Command cmd) {
setId(ready.getHostId());
}

verifyAgentArch(ready.getArch());
processManagementServerList(ready.getMsHostList(), ready.getLbAlgorithm(), ready.getLbCheckInterval());

logger.info("Ready command is processed for agent id = {}", getId());
}

private void verifyAgentArch(String arch) {
if (StringUtils.isNotBlank(arch)) {
String agentArch = getAgentArch();
if (!arch.equals(agentArch)) {
logger.error("Unexpected arch {}, expected {}", agentArch, arch);
}
}
}

public void processOtherTask(final Task task) {
final Object obj = task.get();
if (obj instanceof Response) {
Expand Down
67 changes: 67 additions & 0 deletions api/src/main/java/com/cloud/cpu/CPU.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 com.cloud.cpu;

import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.commons.lang3.StringUtils;

import java.util.LinkedHashMap;
import java.util.Map;

public class CPU {

public static final String archX86Identifier = "i686";
public static final String archX86_64Identifier = "x86_64";
public static final String archARM64Identifier = "aarch64";

public static class CPUArch {
private static final Map<String, CPUArch> cpuArchMap = new LinkedHashMap<>();

public static final CPUArch archX86 = new CPUArch(archX86Identifier, 32);
public static final CPUArch amd64 = new CPUArch(archX86_64Identifier, 64);
public static final CPUArch arm64 = new CPUArch(archARM64Identifier, 64);

private String type;
private int bits;

public CPUArch(String type, int bits) {
this.type = type;
this.bits = bits;
cpuArchMap.put(type, this);
}

public String getType() {
return this.type;
}

public int getBits() {
return this.bits;
}

public static CPUArch fromType(String type) {
if (StringUtils.isBlank(type)) {
return amd64;
}
switch (type) {
case archX86Identifier: return archX86;
case archX86_64Identifier: return amd64;
case archARM64Identifier: return arm64;
default: throw new CloudRuntimeException(String.format("Unsupported arch type: %s", type));
}
}
}
}
3 changes: 3 additions & 0 deletions api/src/main/java/com/cloud/host/Host.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package com.cloud.host;

import com.cloud.cpu.CPU;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.resource.ResourceState;
import com.cloud.utils.fsm.StateObject;
Expand Down Expand Up @@ -208,4 +209,6 @@ public static String[] toStrings(Host.Type... types) {
boolean isDisabled();

ResourceState getResourceState();

CPU.CPUArch getArch();
}
3 changes: 3 additions & 0 deletions api/src/main/java/com/cloud/org/Cluster.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package com.cloud.org;

import com.cloud.cpu.CPU;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.org.Managed.ManagedState;
import org.apache.cloudstack.kernel.Partition;
Expand All @@ -38,4 +39,6 @@ public static enum ClusterType {
AllocationState getAllocationState();

ManagedState getManagedState();

CPU.CPUArch getArch();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Date;
import java.util.Map;

import com.cloud.cpu.CPU;
import com.cloud.user.UserData;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
Expand Down Expand Up @@ -148,4 +149,6 @@ public enum TemplateFilter {

UserData.UserDataOverridePolicy getUserDataOverridePolicy();

CPU.CPUArch getArch();

}
3 changes: 3 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 @@ -36,6 +36,7 @@ public class ApiConstants {
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
public static final String ARCHIVED = "archived";
public static final String ARCH = "arch";
public static final String AS_NUMBER = "asnumber";
public static final String AS_NUMBER_ID = "asnumberid";
public static final String ASN_RANGE = "asnrange";
Expand Down Expand Up @@ -326,6 +327,8 @@ public class ApiConstants {
public static final String MIGRATIONS = "migrations";
public static final String MEMORY = "memory";
public static final String MODE = "mode";
public static final String MULTI_ARCH = "ismultiarch";
public static final String NSX_MODE = "nsxmode";
public static final String NETWORK_MODE = "networkmode";
public static final String NSX_ENABLED = "isnsxenabled";
public static final String NAME = "name";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
// under the License.
package org.apache.cloudstack.api;

import com.cloud.cpu.CPU;
import org.apache.cloudstack.api.response.GuestOSResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.commons.lang3.StringUtils;

import java.util.Collection;
import java.util.Map;
Expand Down Expand Up @@ -77,6 +79,11 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd {
description = "optional boolean field, which indicates if details should be cleaned up or not (if set to true, details removed for this resource, details field ignored; if false or not set, no action)")
private Boolean cleanupDetails;

@Parameter(name = ApiConstants.ARCH, type = CommandType.STRING,
description = "the CPU arch of the template/ISO. Valid options are: x86_64, aarch64",
since = "4.20")
private String arch;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -141,4 +148,11 @@ public Map getDetails() {
public boolean isCleanupDetails(){
return cleanupDetails == null ? false : cleanupDetails.booleanValue();
}

public CPU.CPUArch getCPUArch() {
if (StringUtils.isBlank(arch)) {
return null;
}
return CPU.CPUArch.fromType(arch);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.List;

import com.cloud.cpu.CPU;
import org.apache.cloudstack.api.ApiCommandResourceType;

import org.apache.cloudstack.api.APICommand;
Expand Down Expand Up @@ -67,6 +68,11 @@ public class AddClusterCmd extends BaseCmd {
description = "hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3")
private String hypervisor;

@Parameter(name = ApiConstants.ARCH, type = CommandType.STRING,
description = "the CPU arch of the cluster. Valid options are: x86_64, aarch64",
since = "4.20")
private String arch;

@Parameter(name = ApiConstants.CLUSTER_TYPE, type = CommandType.STRING, required = true, description = "type of the cluster: CloudManaged, ExternalManaged")
private String clusterType;

Expand Down Expand Up @@ -204,6 +210,10 @@ public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.Cluster;
}

public CPU.CPUArch getArch() {
return CPU.CPUArch.fromType(arch);
}

@Override
public void execute() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.api.command.admin.cluster;

import com.cloud.cpu.CPU;
import org.apache.cloudstack.api.ApiCommandResourceType;

import org.apache.cloudstack.api.APICommand;
Expand All @@ -29,6 +30,7 @@
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.org.Cluster;
import com.cloud.user.Account;
import org.apache.commons.lang3.StringUtils;

@APICommand(name = "updateCluster", description = "Updates an existing cluster", responseObject = ClusterResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
Expand All @@ -53,6 +55,11 @@ public class UpdateClusterCmd extends BaseCmd {
@Parameter(name = ApiConstants.MANAGED_STATE, type = CommandType.STRING, description = "whether this cluster is managed by cloudstack")
private String managedState;

@Parameter(name = ApiConstants.ARCH, type = CommandType.STRING,
description = "the CPU arch of the cluster. Valid options are: x86_64, aarch64",
since = "4.20")
private String arch;

public String getClusterName() {
return clusterName;
}
Expand Down Expand Up @@ -108,6 +115,13 @@ public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.Cluster;
}

public CPU.CPUArch getArch() {
if (StringUtils.isBlank(arch)) {
return null;
}
return CPU.CPUArch.fromType(arch);
}

@Override
public void execute() {
Cluster cluster = _resourceService.getCluster(getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.api.command.user.iso;

import com.cloud.cpu.CPU;
import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceTag;
import org.apache.cloudstack.api.response.ResourceIconResponse;
Expand All @@ -34,6 +35,7 @@

import com.cloud.template.VirtualMachineTemplate.TemplateFilter;
import com.cloud.user.Account;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

Expand Down Expand Up @@ -88,6 +90,11 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd {
@Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the isos")
private Boolean showIcon;

@Parameter(name = ApiConstants.ARCH, type = CommandType.STRING,
description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64",
since = "4.20")
private String arch;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -159,6 +166,13 @@ public boolean listInReadyState() {
return onlyReady;
}

public CPU.CPUArch getArch() {
if (StringUtils.isBlank(arch)) {
return null;
}
return CPU.CPUArch.fromType(arch);
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.List;

import com.cloud.cpu.CPU;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
Expand Down Expand Up @@ -118,6 +119,11 @@ public class RegisterIsoCmd extends BaseCmd implements UserCmd {
description = "true if password reset feature is supported; default is false")
private Boolean passwordEnabled;

@Parameter(name = ApiConstants.ARCH, type = CommandType.STRING,
description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64",
since = "4.20")
private String arch;

/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
Expand Down Expand Up @@ -229,6 +235,14 @@ public boolean isPasswordEnabled() {
return passwordEnabled == null ? false : passwordEnabled;
}

public void setArch(String arch) {
this.arch = arch;
}

public CPU.CPUArch getArch() {
return CPU.CPUArch.fromType(arch);
}

/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Collection;
import java.util.Map;

import com.cloud.cpu.CPU;
import com.cloud.hypervisor.Hypervisor;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
Expand Down Expand Up @@ -55,6 +56,11 @@ public class GetUploadParamsForTemplateCmd extends AbstractGetUploadParamsCmd {
description = "the ID of the OS Type that best represents the OS of this template. Not required for VMware as the guest OS is obtained from the OVF file.")
private Long osTypeId;

@Parameter(name = ApiConstants.ARCH, type = CommandType.STRING,
description = "the CPU arch of the template. Valid options are: x86_64, aarch64",
since = "4.20")
private String arch;

@Parameter(name = ApiConstants.BITS, type = CommandType.INTEGER, description = "32 or 64 bits support. 64 by default")
private Integer bits;

Expand Down Expand Up @@ -162,6 +168,10 @@ public boolean isDeployAsIs() {
Boolean.TRUE.equals(deployAsIs);
}

public CPU.CPUArch getArch() {
return CPU.CPUArch.fromType(arch);
}

@Override
public void execute() throws ServerApiException {
validateRequest();
Expand Down
Loading

0 comments on commit 8c8d115

Please sign in to comment.