diff --git a/build/pom.xml b/build/pom.xml
index 5a097bb382a..57d54f39046 100755
--- a/build/pom.xml
+++ b/build/pom.xml
@@ -631,6 +631,11 @@
hostNetworkInterface
${project.version}
+
+ org.zstack
+ physicalServer
+ ${project.version}
+
org.zstack
ovn
@@ -977,6 +982,11 @@
hostNetworkInterface
${project.version}
+
+ org.zstack
+ physicalServer
+ ${project.version}
+
org.zstack
observabilityServer
diff --git a/conf/db/upgrade/V5.5.38__schema.sql b/conf/db/upgrade/V5.5.38__schema.sql
index 1b8937fe5e9..c666c0051a4 100644
--- a/conf/db/upgrade/V5.5.38__schema.sql
+++ b/conf/db/upgrade/V5.5.38__schema.sql
@@ -20,3 +20,88 @@ CREATE TABLE IF NOT EXISTS `zstack`.`SnmpEngineVO` (
`lastOpDate` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `zstack`.`PhysicalServerVO` (
+ `uuid` varchar(32) NOT NULL,
+ `zoneUuid` varchar(32) DEFAULT NULL,
+ `serialNumber` varchar(255) NOT NULL,
+ `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
+ `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `ukPhysicalServerSerialNumber` (`serialNumber`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `zstack`.`PhysicalServerResourceAssignmentVO` (
+ `uuid` varchar(32) NOT NULL,
+ `serverUuid` varchar(32) NOT NULL,
+ `roleType` varchar(64) NOT NULL,
+ `cpuSet` varchar(4096) NOT NULL DEFAULT '',
+ `memory` bigint unsigned DEFAULT NULL,
+ `state` varchar(32) NOT NULL,
+ `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
+ `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`uuid`),
+ UNIQUE KEY `ukPhysicalServerResourceAssignment` (`serverUuid`, `roleType`),
+ CONSTRAINT `fkPhysicalServerResourceAssignmentServerUuid`
+ FOREIGN KEY (`serverUuid`) REFERENCES `zstack`.`PhysicalServerVO` (`uuid`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CALL ADD_COLUMN('HostEO', 'serverUuid', 'VARCHAR(32)', 1, NULL);
+CALL ADD_COLUMN('ManagementNodeVO', 'serverUuid', 'VARCHAR(32)', 1, NULL);
+
+UPDATE `zstack`.`HostEO`
+SET `serverUuid` = NULL
+WHERE `deleted` IS NOT NULL AND `serverUuid` IS NOT NULL;
+
+DROP PROCEDURE IF EXISTS addPhysicalServerIdentityUniqueKeys;
+DELIMITER $$
+CREATE PROCEDURE addPhysicalServerIdentityUniqueKeys()
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.statistics
+ WHERE table_schema = 'zstack'
+ AND table_name = 'HostEO'
+ AND index_name = 'ukHostEOServerUuid'
+ ) THEN
+ ALTER TABLE `zstack`.`HostEO`
+ ADD UNIQUE KEY `ukHostEOServerUuid` (`serverUuid`);
+ END IF;
+
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.statistics
+ WHERE table_schema = 'zstack'
+ AND table_name = 'ManagementNodeVO'
+ AND index_name = 'ukManagementNodeVOServerUuid'
+ ) THEN
+ ALTER TABLE `zstack`.`ManagementNodeVO`
+ ADD UNIQUE KEY `ukManagementNodeVOServerUuid` (`serverUuid`);
+ END IF;
+END $$
+DELIMITER ;
+CALL addPhysicalServerIdentityUniqueKeys();
+DROP PROCEDURE IF EXISTS addPhysicalServerIdentityUniqueKeys;
+
+CALL ADD_CONSTRAINT(
+ 'HostEO',
+ 'fkHostEOServerUuid',
+ 'serverUuid',
+ 'PhysicalServerVO',
+ 'uuid',
+ 'SET NULL'
+);
+
+DROP VIEW IF EXISTS `zstack`.`HostVO`;
+CREATE VIEW `zstack`.`HostVO` AS
+ SELECT uuid, zoneUuid, clusterUuid, name, description, managementIp, hypervisorType,
+ state, status, createDate, lastOpDate, architecture, serverUuid
+ FROM `zstack`.`HostEO`
+ WHERE deleted IS NULL;
+
+CALL ADD_CONSTRAINT(
+ 'ManagementNodeVO',
+ 'fkManagementNodeVOServerUuid',
+ 'serverUuid',
+ 'PhysicalServerVO',
+ 'uuid',
+ 'SET NULL'
+);
diff --git a/conf/persistence.xml b/conf/persistence.xml
index e76141af3ad..371ebd523cc 100755
--- a/conf/persistence.xml
+++ b/conf/persistence.xml
@@ -229,5 +229,7 @@
org.zstack.network.hostNetworkInterface.PhysicalSwitchVO
org.zstack.network.hostNetworkInterface.PhysicalSwitchPortVO
org.zstack.header.core.external.service.ExternalServiceConfigurationVO
+ org.zstack.physicalserver.PhysicalServerVO
+ org.zstack.physicalserver.PhysicalServerResourceAssignmentVO
diff --git a/conf/serviceConfig/physicalServer.xml b/conf/serviceConfig/physicalServer.xml
new file mode 100644
index 00000000000..f72bf1e9f39
--- /dev/null
+++ b/conf/serviceConfig/physicalServer.xml
@@ -0,0 +1,27 @@
+
+
+ physicalServer
+ PhysicalServerApiInterceptor
+
+
+ org.zstack.physicalserver.APIQueryPhysicalServerMsg
+ query
+
+
+
+ org.zstack.physicalserver.APIQueryPhysicalServerResourceAssignmentMsg
+ query
+
+
+
+ org.zstack.physicalserver.APIGetPhysicalServerManagedServicesMsg
+
+
+
+ org.zstack.physicalserver.APIUpdatePhysicalServerResourceAssignmentMsg
+
+
+
+ org.zstack.physicalserver.APIRefreshPhysicalServerResourceAssignmentsMsg
+
+
diff --git a/conf/springConfigXml/HostManager.xml b/conf/springConfigXml/HostManager.xml
index 8147620c4ae..67605682a22 100755
--- a/conf/springConfigXml/HostManager.xml
+++ b/conf/springConfigXml/HostManager.xml
@@ -74,4 +74,4 @@
-
+
diff --git a/conf/springConfigXml/Kvm.xml b/conf/springConfigXml/Kvm.xml
index a96965fc01b..d1124099a69 100755
--- a/conf/springConfigXml/Kvm.xml
+++ b/conf/springConfigXml/Kvm.xml
@@ -115,6 +115,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/springConfigXml/ManagementNodeManager.xml b/conf/springConfigXml/ManagementNodeManager.xml
index ab2d7eefaa1..f861397dd7a 100755
--- a/conf/springConfigXml/ManagementNodeManager.xml
+++ b/conf/springConfigXml/ManagementNodeManager.xml
@@ -17,4 +17,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/springConfigXml/physicalServer.xml b/conf/springConfigXml/physicalServer.xml
new file mode 100644
index 00000000000..06cc6225caa
--- /dev/null
+++ b/conf/springConfigXml/physicalServer.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/springConfigXml/zbs.xml b/conf/springConfigXml/zbs.xml
index 2e0234ddc1d..329c01bb33c 100644
--- a/conf/springConfigXml/zbs.xml
+++ b/conf/springConfigXml/zbs.xml
@@ -29,4 +29,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/zstack.xml b/conf/zstack.xml
index 17bbdfae090..a194805937e 100755
--- a/conf/zstack.xml
+++ b/conf/zstack.xml
@@ -46,6 +46,7 @@
+
diff --git a/core/src/main/java/org/zstack/core/Platform.java b/core/src/main/java/org/zstack/core/Platform.java
index 34df20e3e12..9bb9913dbf7 100755
--- a/core/src/main/java/org/zstack/core/Platform.java
+++ b/core/src/main/java/org/zstack/core/Platform.java
@@ -120,8 +120,10 @@ public class Platform {
};
private static final List UNUSABLE_MACHINE_IDENTITIES = Arrays.asList(
"none",
+ "n/a",
"unknown",
"not specified",
+ "to be filled",
"to be filled by o.e.m.",
"to be filled by oem",
"default string",
@@ -896,6 +898,18 @@ private static String getManagementServerFingerprint() {
return MANAGEMENT_SERVER_FINGERPRINT_ALGORITHM + DigestUtils.sha256Hex(StringUtils.join(identities, "\n"));
}
+ public static String getManagementServerSerialNumber() {
+ String serialNumber = normalizeMachineSerialNumber(
+ readMachineIdentity("/sys/class/dmi/id/product_serial"));
+ return serialNumber == null
+ ? normalizeMachineSerialNumber(readDmiMachineIdentity("system-serial-number"))
+ : serialNumber;
+ }
+
+ public static String normalizeMachineSerialNumber(String serialNumber) {
+ return normalizeMachineIdentity(serialNumber);
+ }
+
private static String readMachineIdentity(String path) {
File file = new File(path);
if (!file.isFile() || !file.canRead()) {
diff --git a/header/src/main/java/org/zstack/header/host/HostAO.java b/header/src/main/java/org/zstack/header/host/HostAO.java
index 8f7bd58ef95..52c2f11a6f6 100755
--- a/header/src/main/java/org/zstack/header/host/HostAO.java
+++ b/header/src/main/java/org/zstack/header/host/HostAO.java
@@ -37,6 +37,9 @@ public class HostAO extends ResourceVO {
@Column
private String architecture;
+ @Column
+ private String serverUuid;
+
@Column
@Enumerated(EnumType.STRING)
private HostState state;
@@ -146,4 +149,12 @@ public String getArchitecture() {
public void setArchitecture(String architecture) {
this.architecture = architecture;
}
+
+ public String getServerUuid() {
+ return serverUuid;
+ }
+
+ public void setServerUuid(String serverUuid) {
+ this.serverUuid = serverUuid;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/host/HostAO_.java b/header/src/main/java/org/zstack/header/host/HostAO_.java
index 921e2b5ac64..c6568126895 100755
--- a/header/src/main/java/org/zstack/header/host/HostAO_.java
+++ b/header/src/main/java/org/zstack/header/host/HostAO_.java
@@ -21,4 +21,5 @@ public class HostAO_ extends ResourceVO_ {
public static volatile SingularAttribute createDate;
public static volatile SingularAttribute lastOpDate;
public static volatile SingularAttribute architecture;
+ public static volatile SingularAttribute serverUuid;
}
diff --git a/header/src/main/java/org/zstack/header/host/HostInventory.java b/header/src/main/java/org/zstack/header/host/HostInventory.java
index 35958b05003..38d6baf8f93 100755
--- a/header/src/main/java/org/zstack/header/host/HostInventory.java
+++ b/header/src/main/java/org/zstack/header/host/HostInventory.java
@@ -185,6 +185,8 @@ public class HostInventory implements Serializable {
private String architecture;
+ private String serverUuid;
+
/**
* @desc the time this resource gets created
*/
@@ -207,6 +209,7 @@ protected HostInventory(HostVO vo) {
this.setZoneUuid(vo.getZoneUuid());
this.setClusterUuid(vo.getClusterUuid());
this.setArchitecture(vo.getArchitecture());
+ this.setServerUuid(vo.getServerUuid());
if (vo.getCapacity() != null) {
this.setTotalCpuCapacity(vo.getCapacity().getTotalCpu());
this.setAvailableCpuCapacity(vo.getCapacity().getAvailableCpu());
@@ -395,6 +398,14 @@ public void setArchitecture(String architecture) {
this.architecture = architecture;
}
+ public String getServerUuid() {
+ return serverUuid;
+ }
+
+ public void setServerUuid(String serverUuid) {
+ this.serverUuid = serverUuid;
+ }
+
public String getIpmiAddress() {
return ipmiAddress;
}
diff --git a/header/src/main/java/org/zstack/header/host/HostNUMANode.java b/header/src/main/java/org/zstack/header/host/HostNUMANode.java
index 733a90cc828..39993485e36 100644
--- a/header/src/main/java/org/zstack/header/host/HostNUMANode.java
+++ b/header/src/main/java/org/zstack/header/host/HostNUMANode.java
@@ -7,6 +7,8 @@
public class HostNUMANode {
public List distance;
public List cpus;
+ public List onlineCpus;
+ public List> coreGroups;
public Long free;
public Long size;
public String nodeID;
@@ -36,6 +38,22 @@ public void setCpus(List cpus) {
this.cpus = cpus;
}
+ public List getOnlineCpus() {
+ return onlineCpus;
+ }
+
+ public void setOnlineCpus(List onlineCpus) {
+ this.onlineCpus = onlineCpus;
+ }
+
+ public List> getCoreGroups() {
+ return coreGroups;
+ }
+
+ public void setCoreGroups(List> coreGroups) {
+ this.coreGroups = coreGroups;
+ }
+
public Long getFree() {
return free;
}
diff --git a/header/src/main/java/org/zstack/header/host/HostVO.java b/header/src/main/java/org/zstack/header/host/HostVO.java
index 319c26750c4..48f0067e435 100755
--- a/header/src/main/java/org/zstack/header/host/HostVO.java
+++ b/header/src/main/java/org/zstack/header/host/HostVO.java
@@ -80,6 +80,6 @@ protected HostVO(HostVO vo) {
this.setCapacity(vo.getCapacity());
this.setIpmi(vo.getIpmi());
this.setHwMonitorStatus(vo.getHwMonitorStatus());
+ this.setServerUuid(vo.getServerUuid());
}
}
-
diff --git a/header/src/main/java/org/zstack/header/managementnode/ManagementNodeInventory.java b/header/src/main/java/org/zstack/header/managementnode/ManagementNodeInventory.java
index 97ce31aec22..f19d8a74c8b 100755
--- a/header/src/main/java/org/zstack/header/managementnode/ManagementNodeInventory.java
+++ b/header/src/main/java/org/zstack/header/managementnode/ManagementNodeInventory.java
@@ -17,6 +17,7 @@ public class ManagementNodeInventory {
private String hostName;
private Timestamp joinDate;
private Timestamp heartBeat;
+ private String serverUuid;
public static ManagementNodeInventory valueOf(ManagementNodeVO vo) {
ManagementNodeInventory inv = new ManagementNodeInventory();
@@ -24,6 +25,7 @@ public static ManagementNodeInventory valueOf(ManagementNodeVO vo) {
inv.setHostName(vo.getHostName());
inv.setJoinDate(vo.getJoinDate());
inv.setUuid(vo.getUuid());
+ inv.setServerUuid(vo.getServerUuid());
return inv;
}
@@ -66,4 +68,12 @@ public Timestamp getJoinDate() {
public Timestamp getHeartBeat() {
return heartBeat;
}
+
+ public String getServerUuid() {
+ return serverUuid;
+ }
+
+ public void setServerUuid(String serverUuid) {
+ this.serverUuid = serverUuid;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO.java b/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO.java
index bd89bcbbf3d..d82cb6ec0e9 100755
--- a/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO.java
+++ b/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO.java
@@ -26,6 +26,9 @@ public class ManagementNodeVO {
@Column
private int port;
+ @Column
+ private String serverUuid;
+
public String getHostName() {
return hostName;
}
@@ -73,4 +76,12 @@ public void setJoinDate(Timestamp joinDate) {
public void setHeartBeat(Timestamp heartBeat) {
this.heartBeat = heartBeat;
}
+
+ public String getServerUuid() {
+ return serverUuid;
+ }
+
+ public void setServerUuid(String serverUuid) {
+ this.serverUuid = serverUuid;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO_.java b/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO_.java
index 70035f974cc..81d98c0450f 100755
--- a/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO_.java
+++ b/header/src/main/java/org/zstack/header/managementnode/ManagementNodeVO_.java
@@ -13,4 +13,5 @@ public class ManagementNodeVO_ {
public static volatile SingularAttribute joinDate;
public static volatile SingularAttribute heartBeat;
public static volatile SingularAttribute state;
+ public static volatile SingularAttribute serverUuid;
}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/ManagedServiceResourceUsage.java b/header/src/main/java/org/zstack/header/physicalserver/ManagedServiceResourceUsage.java
new file mode 100644
index 00000000000..8519574014a
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/ManagedServiceResourceUsage.java
@@ -0,0 +1,76 @@
+package org.zstack.header.physicalserver;
+
+public class ManagedServiceResourceUsage {
+ private String roleType;
+ private String serviceName;
+ private boolean restartable;
+ private String state;
+ private String cpuSet;
+ private Long cpuTime;
+ private Long memory;
+ private Long memoryLimit;
+
+ public String getRoleType() {
+ return roleType;
+ }
+
+ public void setRoleType(String roleType) {
+ this.roleType = roleType;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public boolean isRestartable() {
+ return restartable;
+ }
+
+ public void setRestartable(boolean restartable) {
+ this.restartable = restartable;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getCpuSet() {
+ return cpuSet;
+ }
+
+ public void setCpuSet(String cpuSet) {
+ this.cpuSet = cpuSet;
+ }
+
+ public Long getCpuTime() {
+ return cpuTime;
+ }
+
+ public void setCpuTime(Long cpuTime) {
+ this.cpuTime = cpuTime;
+ }
+
+ public Long getMemory() {
+ return memory;
+ }
+
+ public void setMemory(Long memory) {
+ this.memory = memory;
+ }
+
+ public Long getMemoryLimit() {
+ return memoryLimit;
+ }
+
+ public void setMemoryLimit(Long memoryLimit) {
+ this.memoryLimit = memoryLimit;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerCpuSet.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerCpuSet.java
new file mode 100644
index 00000000000..7358acb290d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerCpuSet.java
@@ -0,0 +1,199 @@
+package org.zstack.header.physicalserver;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.math.BigInteger;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public final class PhysicalServerCpuSet {
+ private PhysicalServerCpuSet() {
+ }
+
+ public static String normalize(String value) {
+ return formatRanges(parseRanges(value));
+ }
+
+ public static SortedSet parse(String value, Set allowed) {
+ List ranges = parseRanges(value);
+ SortedSet cpus = new TreeSet<>();
+ for (Range range : ranges) {
+ long width = (long) range.end - range.start + 1;
+ if (width > allowed.size()) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: CPU_SET range[%s-%s] contains CPUs outside the online topology",
+ range.start, range.end));
+ }
+ for (long cursor = range.start; cursor <= range.end; cursor++) {
+ int cpu = (int) cursor;
+ if (!allowed.contains(cpu)) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: CPU[%s] is outside the online topology", cpu));
+ }
+ cpus.add(cpu);
+ }
+ }
+ return cpus;
+ }
+
+ public static SortedSet parse(String value) {
+ List ranges = parseRanges(value);
+ SortedSet cpus = new TreeSet<>();
+ for (Range range : ranges) {
+ long width = (long) range.end - range.start + 1;
+ if (width > 1048576) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: CPU_SET range[%s-%s] is too large",
+ range.start, range.end));
+ }
+ for (long cursor = range.start; cursor <= range.end; cursor++) {
+ cpus.add((int) cursor);
+ }
+ }
+ return cpus;
+ }
+
+ public static String format(Collection cpus) {
+ if (cpus == null || cpus.isEmpty()) {
+ return "";
+ }
+ List sorted = new ArrayList<>(new LinkedHashSet<>(cpus));
+ Collections.sort(sorted);
+ List ranges = new ArrayList<>();
+ int start = sorted.get(0);
+ int end = start;
+ for (int index = 1; index < sorted.size(); index++) {
+ int current = sorted.get(index);
+ if ((long) current == (long) end + 1) {
+ end = current;
+ } else {
+ ranges.add(new Range(start, end));
+ start = current;
+ end = current;
+ }
+ }
+ ranges.add(new Range(start, end));
+ return formatRanges(ranges);
+ }
+
+ public static int count(String value) {
+ if (value == null || value.trim().isEmpty()) {
+ return 0;
+ }
+ long count = 0;
+ for (Range range : parseRanges(value)) {
+ count += (long) range.end - range.start + 1;
+ if (count > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "CPU_SET_INVALID: CPU_SET contains too many CPUs");
+ }
+ }
+ return (int) count;
+ }
+
+ public static String union(String left, String right) {
+ boolean leftEmpty = left == null || left.trim().isEmpty();
+ boolean rightEmpty = right == null || right.trim().isEmpty();
+ if (leftEmpty && rightEmpty) {
+ return "";
+ }
+ if (leftEmpty) {
+ return normalize(right);
+ }
+ if (rightEmpty) {
+ return normalize(left);
+ }
+ return normalize(left + "," + right);
+ }
+
+ private static List parseRanges(String value) {
+ if (value == null || value.trim().isEmpty()) {
+ throw new IllegalArgumentException("CPU_SET_INVALID: CPU_SET cannot be empty");
+ }
+
+ List ranges = new ArrayList<>();
+ for (String item : value.split(",")) {
+ String part = item.trim();
+ if (part.isEmpty()) {
+ throw new IllegalArgumentException(
+ "CPU_SET_INVALID: CPU_SET contains an empty item");
+ }
+
+ String[] range = part.split("-", -1);
+ if (range.length > 2) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: invalid CPU_SET item[%s]", part));
+ }
+
+ int start = parseCpu(range[0]);
+ int end = range.length == 1 ? start : parseCpu(range[1]);
+ if (start > end) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: invalid CPU_SET range[%s]", part));
+ }
+ ranges.add(new Range(start, end));
+ }
+
+ ranges.sort(Comparator.comparingInt(range -> range.start));
+ List mergedRanges = new ArrayList<>();
+ Range merged = ranges.get(0);
+ for (int index = 1; index < ranges.size(); index++) {
+ Range current = ranges.get(index);
+ if ((long) current.start <= (long) merged.end + 1) {
+ merged.end = Math.max(merged.end, current.end);
+ } else {
+ mergedRanges.add(merged);
+ merged = current;
+ }
+ }
+ mergedRanges.add(merged);
+ return mergedRanges;
+ }
+
+ private static String formatRanges(List ranges) {
+ StringBuilder result = new StringBuilder();
+ for (Range range : ranges) {
+ appendRange(result, range.start, range.end);
+ }
+ return result.toString();
+ }
+
+ private static int parseCpu(String value) {
+ String normalized = value.trim();
+ if (!normalized.matches("[0-9]+")) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: invalid CPU id[%s]", value));
+ }
+ BigInteger cpu = new BigInteger(normalized);
+ if (cpu.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_SET_INVALID: CPU id[%s] is too large", value));
+ }
+ return cpu.intValue();
+ }
+
+ private static void appendRange(StringBuilder result, int start, int end) {
+ if (result.length() > 0) {
+ result.append(',');
+ }
+ result.append(start);
+ if (start != end) {
+ result.append('-').append(end);
+ }
+ }
+
+ private static class Range {
+ private final int start;
+ private int end;
+
+ private Range(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerCpuTopology.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerCpuTopology.java
new file mode 100644
index 00000000000..41dfbcbedf4
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerCpuTopology.java
@@ -0,0 +1,129 @@
+package org.zstack.header.physicalserver;
+
+import java.util.ArrayList;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public class PhysicalServerCpuTopology {
+ public static class CoreGroup {
+ private final String numaId;
+ private final SortedSet cpus;
+
+ CoreGroup(String numaId, Collection cpus) {
+ this.numaId = numaId;
+ this.cpus = new TreeSet<>(cpus);
+ }
+
+ public String getNumaId() {
+ return numaId;
+ }
+
+ public SortedSet getCpus() {
+ return new TreeSet<>(cpus);
+ }
+
+ int firstCpu() {
+ return cpus.first();
+ }
+ }
+
+ private final SortedSet onlineCpus;
+ private final List coreGroups;
+
+ private PhysicalServerCpuTopology(SortedSet onlineCpus, List coreGroups) {
+ this.onlineCpus = onlineCpus;
+ this.coreGroups = coreGroups;
+ }
+
+ public static PhysicalServerCpuTopology from(Map numaNodes) {
+ if (numaNodes == null || numaNodes.isEmpty()) {
+ throw new IllegalArgumentException("CPU_TOPOLOGY_UNAVAILABLE: NUMA topology is empty");
+ }
+
+ SortedSet online = new TreeSet<>();
+ List groups = new ArrayList<>();
+ Set grouped = new HashSet<>();
+ Map sortedNodes = new LinkedHashMap<>();
+ numaNodes.entrySet().stream()
+ .sorted(Map.Entry.comparingByKey())
+ .forEach(entry -> sortedNodes.put(entry.getKey(), entry.getValue()));
+
+ for (Map.Entry entry : sortedNodes.entrySet()) {
+ PhysicalServerNumaNode node = entry.getValue();
+ if (node.getOnlineCpus() == null || node.getCoreGroups() == null || node.getCoreGroups().isEmpty()) {
+ throw new IllegalArgumentException(
+ "CPU_TOPOLOGY_UNSUPPORTED: topology provider did not report online CPUs and core sibling groups");
+ }
+
+ Set nodeOnline = parseCpuIds(node.getOnlineCpus());
+ online.addAll(nodeOnline);
+ for (List rawGroup : node.getCoreGroups()) {
+ Set group = parseCpuIds(rawGroup);
+ if (group.isEmpty() || !nodeOnline.containsAll(group)) {
+ throw new IllegalArgumentException("CPU_TOPOLOGY_INVALID: core group is outside its NUMA online CPUs");
+ }
+ for (Integer cpu : group) {
+ if (!grouped.add(cpu)) {
+ throw new IllegalArgumentException(String.format(
+ "CPU_TOPOLOGY_INVALID: CPU[%s] appears in multiple core groups", cpu));
+ }
+ }
+ groups.add(new CoreGroup(entry.getKey(), group));
+ }
+ }
+
+ if (!grouped.equals(online)) {
+ throw new IllegalArgumentException("CPU_TOPOLOGY_INVALID: core groups do not cover every online CPU exactly once");
+ }
+ groups.sort(Comparator.comparingInt(CoreGroup::firstCpu));
+ return new PhysicalServerCpuTopology(online, groups);
+ }
+
+ private static Set parseCpuIds(List values) {
+ Set result = new HashSet<>();
+ for (String value : values) {
+ if (value == null || !value.matches("[0-9]+")) {
+ throw new IllegalArgumentException(String.format("CPU_TOPOLOGY_INVALID: invalid CPU id[%s]", value));
+ }
+ BigInteger cpu = new BigInteger(value);
+ if (cpu.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+ throw new IllegalArgumentException(String.format("CPU_TOPOLOGY_INVALID: CPU id[%s] is too large", value));
+ }
+ result.add(cpu.intValue());
+ }
+ return result;
+ }
+
+ public SortedSet getOnlineCpus() {
+ return new TreeSet<>(onlineCpus);
+ }
+
+ public List getCoreGroups() {
+ return new ArrayList<>(coreGroups);
+ }
+
+ public CoreGroup getCpuZeroGroup() {
+ for (CoreGroup group : coreGroups) {
+ if (group.cpus.contains(0)) {
+ return group;
+ }
+ }
+ throw new IllegalArgumentException("CPU_TOPOLOGY_INVALID: online topology does not contain CPU0");
+ }
+
+ public String fingerprint() {
+ List encoded = new ArrayList<>();
+ for (CoreGroup group : coreGroups) {
+ encoded.add(group.numaId + ":" + PhysicalServerCpuSet.format(group.cpus));
+ }
+ return String.join(";", encoded);
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerIdentitySpec.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerIdentitySpec.java
new file mode 100644
index 00000000000..0c9c6111ee2
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerIdentitySpec.java
@@ -0,0 +1,30 @@
+package org.zstack.header.physicalserver;
+
+public class PhysicalServerIdentitySpec {
+ private String serialNumber;
+ private String zoneUuid;
+
+ public PhysicalServerIdentitySpec() {
+ }
+
+ public PhysicalServerIdentitySpec(String serialNumber, String zoneUuid) {
+ this.serialNumber = serialNumber;
+ this.zoneUuid = zoneUuid;
+ }
+
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ public void setSerialNumber(String serialNumber) {
+ this.serialNumber = serialNumber;
+ }
+
+ public String getZoneUuid() {
+ return zoneUuid;
+ }
+
+ public void setZoneUuid(String zoneUuid) {
+ this.zoneUuid = zoneUuid;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerManager.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerManager.java
new file mode 100644
index 00000000000..723d3b580f1
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerManager.java
@@ -0,0 +1,40 @@
+package org.zstack.header.physicalserver;
+
+import org.zstack.header.core.Completion;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+public interface PhysicalServerManager {
+ Map resolveIdentities(Collection identities);
+
+ Map findSerialNumbersByServerUuids(
+ Collection serverUuids);
+
+ void ensureResourceAssignments(Collection serverUuids, String roleType);
+
+ default void ensureResourceAssignment(String serverUuid, String roleType) {
+ ensureResourceAssignments(Collections.singleton(serverUuid), roleType);
+ }
+
+ void reconcile(String serverUuid, boolean refreshFacts);
+
+ void reconcileAll();
+
+ default void releaseResourceAssignment(
+ String serverUuid,
+ String roleType,
+ String consumerUuid,
+ Completion completion) {
+ releaseResourceAssignment(
+ serverUuid, roleType, consumerUuid, false, completion);
+ }
+
+ void releaseResourceAssignment(
+ String serverUuid,
+ String roleType,
+ String consumerUuid,
+ boolean force,
+ Completion completion);
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerNumaNode.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerNumaNode.java
new file mode 100644
index 00000000000..01725e9b0e6
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerNumaNode.java
@@ -0,0 +1,34 @@
+package org.zstack.header.physicalserver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PhysicalServerNumaNode {
+ private String nodeId;
+ private List onlineCpus = new ArrayList<>();
+ private List> coreGroups = new ArrayList<>();
+
+ public String getNodeId() {
+ return nodeId;
+ }
+
+ public void setNodeId(String nodeId) {
+ this.nodeId = nodeId;
+ }
+
+ public List getOnlineCpus() {
+ return onlineCpus;
+ }
+
+ public void setOnlineCpus(List onlineCpus) {
+ this.onlineCpus = onlineCpus;
+ }
+
+ public List> getCoreGroups() {
+ return coreGroups;
+ }
+
+ public void setCoreGroups(List> coreGroups) {
+ this.coreGroups = coreGroups;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceApplicationMode.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceApplicationMode.java
new file mode 100644
index 00000000000..be58aca894f
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceApplicationMode.java
@@ -0,0 +1,6 @@
+package org.zstack.header.physicalserver;
+
+public enum PhysicalServerResourceApplicationMode {
+ RESOURCE_HANDLES,
+ PROVIDER_MANAGED
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceConsumerState.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceConsumerState.java
new file mode 100644
index 00000000000..ed212af9f8d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceConsumerState.java
@@ -0,0 +1,7 @@
+package org.zstack.header.physicalserver;
+
+public enum PhysicalServerResourceConsumerState {
+ AVAILABLE,
+ UNAVAILABLE,
+ MISSING
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceControlAdapter.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceControlAdapter.java
new file mode 100644
index 00000000000..c23dc66e7e3
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceControlAdapter.java
@@ -0,0 +1,76 @@
+package org.zstack.header.physicalserver;
+
+import org.zstack.header.core.Completion;
+import org.zstack.header.core.ReturnValueCompletion;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+public interface PhysicalServerResourceControlAdapter {
+ String getRoleType();
+
+ PhysicalServerResourceIsolationMode getIsolationMode();
+
+ PhysicalServerResourceApplicationMode getApplicationMode();
+
+ default String getTopologyRoleType() {
+ return getRoleType();
+ }
+
+ default String getDefaultCpuSet(
+ PhysicalServerCpuTopology topology,
+ Set allocatedExclusiveCpus) {
+ Set available = new TreeSet<>(topology.getOnlineCpus());
+ available.removeAll(allocatedExclusiveCpus);
+ return PhysicalServerCpuSet.format(available);
+ }
+
+ default void refreshAssociations() {
+ }
+
+ default void refreshAssociations(Collection serverUuids) {
+ refreshAssociations();
+ }
+
+ default void refreshCapacity(String serverUuid) {
+ }
+
+ Set getAssociatedServerUuids();
+
+ default Set getEligibleDefaultServerUuids() {
+ return getAssociatedServerUuids();
+ }
+
+ PhysicalServerResourceConsumerState getState(String serverUuid);
+
+ default String getUnavailableReason(String serverUuid) {
+ return "RESOURCE_CONSUMER_UNAVAILABLE";
+ }
+
+ Map getStates(
+ Collection serverUuids);
+
+ void collectTopology(
+ String serverUuid,
+ ReturnValueCompletion completion);
+
+ void apply(
+ String serverUuid,
+ String consumerUuid,
+ ResourceControlCommand command,
+ ReturnValueCompletion completion);
+
+ void collectManagedServiceUsage(
+ String serverUuid,
+ boolean includeAuxiliaryServices,
+ ReturnValueCompletion> completion);
+
+ void restartManagedServices(
+ String serverUuid,
+ boolean includeAuxiliaryServices,
+ Collection serviceNames,
+ Completion completion);
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceIsolationMode.java b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceIsolationMode.java
new file mode 100644
index 00000000000..a0265b9aaa3
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/PhysicalServerResourceIsolationMode.java
@@ -0,0 +1,6 @@
+package org.zstack.header.physicalserver;
+
+public enum PhysicalServerResourceIsolationMode {
+ SHARED,
+ EXCLUSIVE
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/ResourceConsumerHandle.java b/header/src/main/java/org/zstack/header/physicalserver/ResourceConsumerHandle.java
new file mode 100644
index 00000000000..67c8f220016
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/ResourceConsumerHandle.java
@@ -0,0 +1,90 @@
+package org.zstack.header.physicalserver;
+
+public class ResourceConsumerHandle {
+ public static final String SYSTEMD_UNIT = "SYSTEMD_UNIT";
+ public static final String OWNER_PID_FILE = "OWNER_PID_FILE";
+
+ private String handleType;
+ private String value;
+ private String serviceName;
+ private String consumerKey;
+ private boolean optional;
+ private boolean restartable;
+ private String expectedCommandToken;
+
+ public ResourceConsumerHandle() {
+ }
+
+ public ResourceConsumerHandle(
+ String handleType,
+ String value,
+ String serviceName,
+ String consumerKey,
+ boolean optional,
+ boolean restartable,
+ String expectedCommandToken) {
+ this.handleType = handleType;
+ this.value = value;
+ this.serviceName = serviceName;
+ this.consumerKey = consumerKey;
+ this.optional = optional;
+ this.restartable = restartable;
+ this.expectedCommandToken = expectedCommandToken;
+ }
+
+ public String getHandleType() {
+ return handleType;
+ }
+
+ public void setHandleType(String handleType) {
+ this.handleType = handleType;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public String getConsumerKey() {
+ return consumerKey;
+ }
+
+ public void setConsumerKey(String consumerKey) {
+ this.consumerKey = consumerKey;
+ }
+
+ public boolean isOptional() {
+ return optional;
+ }
+
+ public void setOptional(boolean optional) {
+ this.optional = optional;
+ }
+
+ public boolean isRestartable() {
+ return restartable;
+ }
+
+ public void setRestartable(boolean restartable) {
+ this.restartable = restartable;
+ }
+
+ public String getExpectedCommandToken() {
+ return expectedCommandToken;
+ }
+
+ public void setExpectedCommandToken(String expectedCommandToken) {
+ this.expectedCommandToken = expectedCommandToken;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/ResourceControlCommand.java b/header/src/main/java/org/zstack/header/physicalserver/ResourceControlCommand.java
new file mode 100644
index 00000000000..89b203bc793
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/ResourceControlCommand.java
@@ -0,0 +1,79 @@
+package org.zstack.header.physicalserver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResourceControlCommand {
+ private String roleType;
+ private String isolationMode;
+ private String operation;
+ private String cpuSet;
+ private Long memory;
+ private String sliceName;
+ private boolean includeAuxiliaryServices;
+ private List handles = new ArrayList<>();
+
+ public String getRoleType() {
+ return roleType;
+ }
+
+ public void setRoleType(String roleType) {
+ this.roleType = roleType;
+ }
+
+ public String getIsolationMode() {
+ return isolationMode;
+ }
+
+ public void setIsolationMode(String isolationMode) {
+ this.isolationMode = isolationMode;
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+
+ public void setOperation(String operation) {
+ this.operation = operation;
+ }
+
+ public String getCpuSet() {
+ return cpuSet;
+ }
+
+ public void setCpuSet(String cpuSet) {
+ this.cpuSet = cpuSet;
+ }
+
+ public Long getMemory() {
+ return memory;
+ }
+
+ public void setMemory(Long memory) {
+ this.memory = memory;
+ }
+
+ public String getSliceName() {
+ return sliceName;
+ }
+
+ public void setSliceName(String sliceName) {
+ this.sliceName = sliceName;
+ }
+
+ public boolean isIncludeAuxiliaryServices() {
+ return includeAuxiliaryServices;
+ }
+
+ public void setIncludeAuxiliaryServices(boolean includeAuxiliaryServices) {
+ this.includeAuxiliaryServices = includeAuxiliaryServices;
+ }
+
+ public List getHandles() {
+ return handles;
+ }
+
+ public void setHandles(List handles) {
+ this.handles = handles;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/ResourceControlResponse.java b/header/src/main/java/org/zstack/header/physicalserver/ResourceControlResponse.java
new file mode 100644
index 00000000000..1df5cdf009c
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/ResourceControlResponse.java
@@ -0,0 +1,61 @@
+package org.zstack.header.physicalserver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResourceControlResponse {
+ private String state;
+ private String cpuSet;
+ private Long memory;
+ private Integer coveredServiceCount;
+ private Integer expectedServiceCount;
+ private List results = new ArrayList<>();
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getCpuSet() {
+ return cpuSet;
+ }
+
+ public void setCpuSet(String cpuSet) {
+ this.cpuSet = cpuSet;
+ }
+
+ public Long getMemory() {
+ return memory;
+ }
+
+ public void setMemory(Long memory) {
+ this.memory = memory;
+ }
+
+ public Integer getCoveredServiceCount() {
+ return coveredServiceCount;
+ }
+
+ public void setCoveredServiceCount(Integer coveredServiceCount) {
+ this.coveredServiceCount = coveredServiceCount;
+ }
+
+ public Integer getExpectedServiceCount() {
+ return expectedServiceCount;
+ }
+
+ public void setExpectedServiceCount(Integer expectedServiceCount) {
+ this.expectedServiceCount = expectedServiceCount;
+ }
+
+ public List getResults() {
+ return results;
+ }
+
+ public void setResults(List results) {
+ this.results = results;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/ResourceControlResult.java b/header/src/main/java/org/zstack/header/physicalserver/ResourceControlResult.java
new file mode 100644
index 00000000000..aa939f3bde3
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/ResourceControlResult.java
@@ -0,0 +1,31 @@
+package org.zstack.header.physicalserver;
+
+public class ResourceControlResult {
+ private String state;
+ private String cpuSet;
+ private Long memory;
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getCpuSet() {
+ return cpuSet;
+ }
+
+ public void setCpuSet(String cpuSet) {
+ this.cpuSet = cpuSet;
+ }
+
+ public Long getMemory() {
+ return memory;
+ }
+
+ public void setMemory(Long memory) {
+ this.memory = memory;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/physicalserver/RoleServiceManifest.java b/header/src/main/java/org/zstack/header/physicalserver/RoleServiceManifest.java
new file mode 100644
index 00000000000..4a08e1240e3
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/physicalserver/RoleServiceManifest.java
@@ -0,0 +1,375 @@
+package org.zstack.header.physicalserver;
+
+import org.zstack.utils.YamlUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class RoleServiceManifest {
+ private String roleType;
+ private String applicationMode;
+ private String sliceName;
+ private List services = new ArrayList<>();
+
+ public static RoleServiceManifest load(
+ String resourcePath,
+ String expectedRoleType,
+ PhysicalServerResourceApplicationMode expectedApplicationMode) {
+ RoleServiceManifest manifest = YamlUtils.load(
+ read(resourcePath), RoleServiceManifest.class);
+ if (manifest == null) {
+ throw new IllegalStateException(String.format(
+ "role service manifest[%s] is empty", resourcePath));
+ }
+ manifest.validate(
+ resourcePath, expectedRoleType, expectedApplicationMode);
+ manifest.services = Collections.unmodifiableList(
+ new ArrayList<>(manifest.services));
+ return manifest;
+ }
+
+ public List handles(
+ String primaryConsumerKey,
+ String auxiliaryConsumerKey,
+ boolean includeAuxiliaryServices) {
+ return handles(
+ primaryConsumerKey,
+ auxiliaryConsumerKey,
+ includeAuxiliaryServices,
+ Collections.emptyMap());
+ }
+
+ public List handles(
+ String primaryConsumerKey,
+ String auxiliaryConsumerKey,
+ boolean includeAuxiliaryServices,
+ Map values) {
+ if (!PhysicalServerResourceApplicationMode.RESOURCE_HANDLES.name()
+ .equals(applicationMode)) {
+ throw new IllegalStateException(String.format(
+ "role[%s] does not use resource handles", roleType));
+ }
+ List result = new ArrayList<>();
+ for (Service service : services) {
+ if (service.isAuxiliary() && !includeAuxiliaryServices) {
+ continue;
+ }
+ result.add(new ResourceConsumerHandle(
+ service.getHandleType(),
+ service.resolveValue(values),
+ service.getName(),
+ service.isAuxiliary()
+ ? auxiliaryConsumerKey : primaryConsumerKey,
+ !service.required(),
+ service.restartable(),
+ service.getExpectedCommandToken()));
+ }
+ return result;
+ }
+
+ public List handlesByServiceNames(
+ Collection serviceNames,
+ String primaryConsumerKey,
+ String auxiliaryConsumerKey,
+ boolean includeAuxiliaryServices,
+ Map values) {
+ if (serviceNames == null || serviceNames.isEmpty()) {
+ throw new IllegalArgumentException("service names must not be empty");
+ }
+ Set selected = new HashSet<>(serviceNames);
+ if (selected.size() != serviceNames.size()) {
+ throw new IllegalArgumentException("service names must not be duplicated");
+ }
+ List available = handles(
+ primaryConsumerKey, auxiliaryConsumerKey,
+ includeAuxiliaryServices, values);
+ List result = available.stream()
+ .filter(handle -> selected.remove(handle.getServiceName()))
+ .collect(Collectors.toList());
+ if (!selected.isEmpty()) {
+ throw new IllegalArgumentException(String.format(
+ "services%s are not defined by role[%s]", selected, roleType));
+ }
+ return result;
+ }
+
+ public List managedServiceUsages(
+ boolean includeAuxiliaryServices, String state) {
+ List result = new ArrayList<>();
+ for (Service service : services) {
+ if (service.isAuxiliary() && !includeAuxiliaryServices) {
+ continue;
+ }
+ ManagedServiceResourceUsage usage =
+ new ManagedServiceResourceUsage();
+ usage.setRoleType(roleType);
+ usage.setServiceName(service.getName());
+ usage.setRestartable(service.restartable());
+ usage.setState(state);
+ result.add(usage);
+ }
+ return result;
+ }
+
+ private void validate(
+ String resourcePath,
+ String expectedRoleType,
+ PhysicalServerResourceApplicationMode expectedApplicationMode) {
+ if (!expectedRoleType.equals(roleType)) {
+ throw invalid(resourcePath, String.format(
+ "roleType[%s] does not match expected roleType[%s]",
+ roleType, expectedRoleType));
+ }
+ if (!expectedApplicationMode.name().equals(applicationMode)) {
+ throw invalid(resourcePath, String.format(
+ "applicationMode[%s] does not match expected mode[%s]",
+ applicationMode, expectedApplicationMode));
+ }
+ if (expectedApplicationMode
+ == PhysicalServerResourceApplicationMode.RESOURCE_HANDLES
+ && (empty(sliceName)
+ || !sliceName.matches("[A-Za-z0-9][A-Za-z0-9_.@:-]{0,248}\\.slice"))) {
+ throw invalid(resourcePath, String.format(
+ "sliceName[%s] is invalid", sliceName));
+ }
+ if (expectedApplicationMode
+ == PhysicalServerResourceApplicationMode.PROVIDER_MANAGED
+ && !empty(sliceName)) {
+ throw invalid(resourcePath,
+ "provider-managed role cannot define sliceName");
+ }
+ if (services == null || services.isEmpty()) {
+ throw invalid(resourcePath, "services must not be empty");
+ }
+ Set names = new HashSet<>();
+ for (Service service : services) {
+ if (service == null || empty(service.getName())
+ || !service.getName().matches(
+ "[A-Za-z0-9][A-Za-z0-9_.-]{0,63}")) {
+ throw invalid(resourcePath, "service name must not be empty");
+ }
+ if (!names.add(service.getName())) {
+ throw invalid(resourcePath, String.format(
+ "service name[%s] is duplicated", service.getName()));
+ }
+ if (expectedApplicationMode
+ == PhysicalServerResourceApplicationMode.RESOURCE_HANDLES) {
+ validateHandle(resourcePath, service);
+ }
+ }
+ }
+
+ private void validateHandle(String resourcePath, Service service) {
+ if (!ResourceConsumerHandle.SYSTEMD_UNIT.equals(service.getHandleType())
+ && !ResourceConsumerHandle.OWNER_PID_FILE.equals(
+ service.getHandleType())) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] has unsupported handleType[%s]",
+ service.getName(), service.getHandleType()));
+ }
+ if (empty(service.getValue()) == empty(service.getValueFrom())) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] must specify exactly one of value and valueFrom",
+ service.getName()));
+ }
+ if (service.getRequired() == null) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] required must be specified", service.getName()));
+ }
+ if (service.getRestartable() == null) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] restartable must be specified",
+ service.getName()));
+ }
+ if (service.restartable()
+ && !ResourceConsumerHandle.SYSTEMD_UNIT.equals(
+ service.getHandleType())) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] restartable requires a systemd handle",
+ service.getName()));
+ }
+ if (ResourceConsumerHandle.OWNER_PID_FILE.equals(
+ service.getHandleType())
+ && empty(service.getExpectedCommandToken())) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] command token must not be empty",
+ service.getName()));
+ }
+ if (ResourceConsumerHandle.SYSTEMD_UNIT.equals(service.getHandleType())
+ && !empty(service.getExpectedCommandToken())) {
+ throw invalid(resourcePath, String.format(
+ "service[%s] systemd handle cannot have a command token",
+ service.getName()));
+ }
+ }
+
+ private static IllegalStateException invalid(
+ String resourcePath, String reason) {
+ return new IllegalStateException(String.format(
+ "invalid role service manifest[%s]: %s", resourcePath, reason));
+ }
+
+ private static boolean empty(String value) {
+ return value == null || value.trim().isEmpty();
+ }
+
+ private static String read(String resourcePath) {
+ ClassLoader context = Thread.currentThread().getContextClassLoader();
+ InputStream stream = context == null
+ ? null : context.getResourceAsStream(resourcePath);
+ if (stream == null) {
+ stream = RoleServiceManifest.class.getClassLoader()
+ .getResourceAsStream(resourcePath);
+ }
+ if (stream == null) {
+ throw new IllegalStateException(String.format(
+ "role service manifest[%s] was not found", resourcePath));
+ }
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(
+ stream, StandardCharsets.UTF_8))) {
+ return reader.lines().collect(Collectors.joining("\n"));
+ } catch (IOException error) {
+ throw new IllegalStateException(String.format(
+ "cannot read role service manifest[%s]", resourcePath), error);
+ }
+ }
+
+ public String getRoleType() {
+ return roleType;
+ }
+
+ public void setRoleType(String roleType) {
+ this.roleType = roleType;
+ }
+
+ public String getApplicationMode() {
+ return applicationMode;
+ }
+
+ public void setApplicationMode(String applicationMode) {
+ this.applicationMode = applicationMode;
+ }
+
+ public String getSliceName() {
+ return sliceName;
+ }
+
+ public void setSliceName(String sliceName) {
+ this.sliceName = sliceName;
+ }
+
+ public List getServices() {
+ return services;
+ }
+
+ public void setServices(List services) {
+ this.services = services;
+ }
+
+ public static class Service {
+ private String name;
+ private String handleType;
+ private String value;
+ private String valueFrom;
+ private Boolean required;
+ private Boolean restartable;
+ private boolean auxiliary;
+ private String expectedCommandToken;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getHandleType() {
+ return handleType;
+ }
+
+ public void setHandleType(String handleType) {
+ this.handleType = handleType;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getValueFrom() {
+ return valueFrom;
+ }
+
+ public void setValueFrom(String valueFrom) {
+ this.valueFrom = valueFrom;
+ }
+
+ private String resolveValue(Map values) {
+ if (!empty(value)) {
+ return value;
+ }
+ String resolved = values.get(valueFrom);
+ if (empty(resolved)) {
+ throw new IllegalStateException(String.format(
+ "role service[%s] valueFrom[%s] cannot be resolved",
+ name, valueFrom));
+ }
+ return resolved;
+ }
+
+ public Boolean getRequired() {
+ return required;
+ }
+
+ public void setRequired(Boolean required) {
+ this.required = required;
+ }
+
+ private boolean required() {
+ return Boolean.TRUE.equals(required);
+ }
+
+ public Boolean getRestartable() {
+ return restartable;
+ }
+
+ public void setRestartable(Boolean restartable) {
+ this.restartable = restartable;
+ }
+
+ private boolean restartable() {
+ return Boolean.TRUE.equals(restartable);
+ }
+
+ public boolean isAuxiliary() {
+ return auxiliary;
+ }
+
+ public void setAuxiliary(boolean auxiliary) {
+ this.auxiliary = auxiliary;
+ }
+
+ public String getExpectedCommandToken() {
+ return expectedCommandToken;
+ }
+
+ public void setExpectedCommandToken(String expectedCommandToken) {
+ this.expectedCommandToken = expectedCommandToken;
+ }
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java b/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java
index feb4c5c11c7..daedf838732 100644
--- a/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java
+++ b/header/src/main/java/org/zstack/header/storage/addon/primary/PrimaryStorageControllerSvc.java
@@ -43,6 +43,10 @@ public interface PrimaryStorageControllerSvc {
*/
void syncConfig(String config);
+ default void beforePersistAddonInfo(AddonInfo addonInfo, Completion completion) {
+ completion.success();
+ }
+
void getCapacity(List requiredUrls, ReturnValueCompletion comp);
void reportCapacity(ReturnValueCompletion comp);
void reportHealthy(ReturnValueCompletion comp);
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostCapacityExtension.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostCapacityExtension.java
index 16ff667f21d..de9e012e022 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostCapacityExtension.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostCapacityExtension.java
@@ -30,10 +30,14 @@ public class KVMHostCapacityExtension implements KVMHostConnectExtensionPoint, H
private ResourceConfigFacade rcf;
public void reportCapacity(HostInventory host, Completion completion) {
+ reportCapacity(host.getUuid(), completion);
+ }
+
+ public void reportCapacity(String hostUuid, Completion completion) {
CheckHostCapacityMsg msg = new CheckHostCapacityMsg();
- msg.setHostUuid(host.getUuid());
+ msg.setHostUuid(hostUuid);
msg.setTimeout(TimeUnit.MINUTES.toMillis(30));
- bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, host.getUuid());
+ bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, hostUuid);
bus.send(msg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply rly) {
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KvmPhysicalServerAdapter.java b/plugin/kvm/src/main/java/org/zstack/kvm/KvmPhysicalServerAdapter.java
new file mode 100644
index 00000000000..53e729dc960
--- /dev/null
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KvmPhysicalServerAdapter.java
@@ -0,0 +1,988 @@
+package org.zstack.kvm;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.zstack.compute.host.HostSystemTags;
+import org.zstack.compute.host.PostHostConnectExtensionPoint;
+import org.zstack.core.Platform;
+import org.zstack.core.cloudbus.CloudBus;
+import org.zstack.core.cloudbus.CloudBusCallBack;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.Q;
+import org.zstack.header.Component;
+import org.zstack.header.core.Completion;
+import org.zstack.header.core.ReturnValueCompletion;
+import org.zstack.header.core.workflow.Flow;
+import org.zstack.header.core.workflow.FlowTrigger;
+import org.zstack.header.core.workflow.NoRollbackFlow;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.errorcode.ErrorableValue;
+import org.zstack.header.host.GetHostNumaTopologyMsg;
+import org.zstack.header.host.GetHostNumaTopologyReply;
+import org.zstack.header.host.HostAO_;
+import org.zstack.header.host.HostAfterConnectedExtensionPoint;
+import org.zstack.header.host.HostConstant;
+import org.zstack.header.host.HostDeleteExtensionPoint;
+import org.zstack.header.host.HostEO;
+import org.zstack.header.host.HostException;
+import org.zstack.header.host.HostInventory;
+import org.zstack.header.host.HostNUMANode;
+import org.zstack.header.host.HostStatus;
+import org.zstack.header.host.HostVO;
+import org.zstack.header.host.HostVO_;
+import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint;
+import org.zstack.header.message.MessageReply;
+import org.zstack.header.physicalserver.PhysicalServerResourceControlAdapter;
+import org.zstack.header.physicalserver.PhysicalServerResourceApplicationMode;
+import org.zstack.header.physicalserver.PhysicalServerResourceConsumerState;
+import org.zstack.header.physicalserver.PhysicalServerResourceIsolationMode;
+import org.zstack.header.physicalserver.PhysicalServerCpuTopology;
+import org.zstack.header.physicalserver.PhysicalServerIdentitySpec;
+import org.zstack.header.physicalserver.PhysicalServerManager;
+import org.zstack.header.physicalserver.ManagedServiceResourceUsage;
+import org.zstack.header.physicalserver.PhysicalServerNumaNode;
+import org.zstack.header.physicalserver.ResourceControlCommand;
+import org.zstack.header.physicalserver.ResourceControlResponse;
+import org.zstack.header.physicalserver.ResourceConsumerHandle;
+import org.zstack.header.physicalserver.ResourceControlResult;
+import org.zstack.header.physicalserver.RoleServiceManifest;
+import org.zstack.header.tag.AbstractSystemTagLifeCycleListener;
+import org.zstack.header.tag.SystemTagInventory;
+import org.zstack.header.tag.SystemTagVO;
+import org.zstack.header.tag.SystemTagVO_;
+import org.zstack.utils.TagUtils;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+import javax.persistence.Query;
+import javax.persistence.Tuple;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.zstack.core.Platform.operr;
+
+public class KvmPhysicalServerAdapter implements
+ PhysicalServerResourceControlAdapter,
+ PostHostConnectExtensionPoint,
+ HostAfterConnectedExtensionPoint,
+ HostDeleteExtensionPoint,
+ ManagementNodeReadyExtensionPoint,
+ Component {
+ public static final String ROLE_TYPE = "COMPUTE";
+ public static final String APPLY_RESOURCE_CONTROL_PATH = "/host/resourcecontrol/apply";
+ public static final String GET_MANAGED_SERVICE_USAGE_PATH =
+ "/host/resourcecontrol/services";
+ public static final String RESTART_MANAGED_SERVICES_PATH =
+ "/host/resourcecontrol/restart";
+ public static final String ROLE_SERVICE_MANIFEST_PATH =
+ "physical-server-roles/compute.yaml";
+ private static final String ERROR_CODE = "ORG_ZSTACK_KVM_10000";
+ private static final int HOST_LINK_UPDATE_BATCH_SIZE = 500;
+ private static final CLogger logger = Utils.getLogger(KvmPhysicalServerAdapter.class);
+ private static final RoleServiceManifest ROLE_SERVICES =
+ RoleServiceManifest.load(
+ ROLE_SERVICE_MANIFEST_PATH,
+ ROLE_TYPE,
+ PhysicalServerResourceApplicationMode.RESOURCE_HANDLES);
+ private final AtomicReference