Skip to content

Commit 17eb17c

Browse files
committed
wip changes for extension sync
Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 41703c2 commit 17eb17c

File tree

10 files changed

+247
-2
lines changed

10 files changed

+247
-2
lines changed

api/src/main/java/org/apache/cloudstack/alert/AlertService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ private AlertType(short type, String name, boolean isDefault) {
7373
public static final AlertType ALERT_TYPE_VM_SNAPSHOT = new AlertType((short)32, "ALERT.VM.SNAPSHOT", true);
7474
public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PUBLIC.IFACE.MTU", true);
7575
public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)32, "ALERT.VR.PRIVATE.IFACE.MTU", true);
76+
public static final AlertType ALERT_TYPE_EXTENSION_ENTRY_POINT_OUT_OF_SYNC = new AlertType((short)33, "ALERT.TYPE.EXTENSION.ENTRY.POINT.OUT.OF.SYNC", true);
7677

7778
public short getType() {
7879
return type;

api/src/main/java/org/apache/cloudstack/extension/Extension.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ enum Type {
3030
String getDescription();
3131
Type getType();
3232
String getRelativeEntryPoint();
33+
boolean isEntryPointSync();
3334
boolean isUserDefined();
3435
Date getCreated();
3536
}

engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ public interface ExternalProvisioner extends Manager {
5050

5151
String getExtensionEntryPoint(String relativeEntryPoint);
5252

53+
String getChecksumForExtensionEntryPoint(String extensionName, String relativeEntryPoint);
54+
5355
void prepareExtensionEntryPoint(String extensionName, boolean userDefined, String extensionRelativeEntryPoint);
5456

5557
PrepareExternalProvisioningAnswer prepareExternalProvisioning(String hostGuid, String extensionName, String extensionRelativeEntryPoint, PrepareExternalProvisioningCommand cmd);

engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
import org.apache.cloudstack.framework.config.ConfigDepot;
4747
import org.apache.cloudstack.framework.config.ConfigKey;
4848
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
49+
import org.apache.cloudstack.framework.extensions.command.GetExtensionEntryPointChecksumCommand;
50+
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
4951
import org.apache.cloudstack.ha.dao.HAConfigDao;
5052
import org.apache.cloudstack.maintenance.ManagementServerMaintenanceManager;
5153
import org.apache.cloudstack.maintenance.command.BaseShutdownManagementServerHostCommand;
@@ -60,6 +62,7 @@
6062
import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao;
6163
import org.apache.cloudstack.utils.identity.ManagementServerNode;
6264
import org.apache.cloudstack.utils.security.SSLUtils;
65+
import org.apache.commons.collections.CollectionUtils;
6366

6467
import com.cloud.agent.api.Answer;
6568
import com.cloud.agent.api.CancelCommand;
@@ -105,8 +108,6 @@
105108
import com.cloud.utils.nio.Task;
106109
import com.google.gson.Gson;
107110

108-
import org.apache.commons.collections.CollectionUtils;
109-
110111
public class ClusteredAgentManagerImpl extends AgentManagerImpl implements ClusterManagerListener, ClusteredAgentRebalanceService {
111112
private static ScheduledExecutorService s_transferExecutor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("Cluster-AgentRebalancingExecutor"));
112113
private final long rebalanceTimeOut = 300000; // 5 mins - after this time remove the agent from the transfer list
@@ -146,6 +147,8 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
146147
private ManagementServerMaintenanceManager managementServerMaintenanceManager;
147148
@Inject
148149
private DataCenterDao dcDao;
150+
@Inject
151+
ExtensionsManager extensionsManager;
149152

150153
protected ClusteredAgentManagerImpl() {
151154
super();
@@ -1321,6 +1324,9 @@ public String dispatch(final ClusterServicePdu pdu) {
13211324
} else if (cmds.length == 1 && cmds[0] instanceof BaseShutdownManagementServerHostCommand) {
13221325
final BaseShutdownManagementServerHostCommand cmd = (BaseShutdownManagementServerHostCommand) cmds[0];
13231326
return handleShutdownManagementServerHostCommand(cmd);
1327+
} else if (cmds.length == 1 && cmds[0] instanceof GetExtensionEntryPointChecksumCommand) {
1328+
final GetExtensionEntryPointChecksumCommand cmd = (GetExtensionEntryPointChecksumCommand) cmds[0];
1329+
return extensionsManager.handleGetExtensionEntryPointChecksumCommand(cmd);
13241330
}
13251331

13261332
try {

engine/schema/src/main/resources/META-INF/db/schema-42010to42100.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ CREATE TABLE IF NOT EXISTS `cloud`.`extension` (
9191
`description` varchar(4096),
9292
`type` varchar(255) NOT NULL COMMENT 'Type of the extension: Orchestrator, etc',
9393
`relative_entry_point` varchar(2048) NOT NULL COMMENT 'Path of entry point for the extension relative to the root extensions directory',
94+
`entry_point_sync` int unsigned DEFAULT '0' COMMENT 'True if the extension entry point is in sync across management servers',
9495
`is_user_defined` int unsigned DEFAULT '0' COMMENT 'True if the extension is added by admin',
9596
`created` datetime NOT NULL,
9697
`removed` datetime DEFAULT NULL,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.framework.extensions.command;
19+
20+
import com.cloud.agent.api.Command;
21+
22+
public class GetExtensionEntryPointChecksumCommand extends Command {
23+
long msId;
24+
long extensionId;
25+
String extensionName;
26+
String extensionRelativeEntryPointPath;
27+
28+
public GetExtensionEntryPointChecksumCommand(long msId, long extensionId, String extensionName,
29+
String extensionRelativeEntryPointPath) {
30+
this.msId = msId;
31+
this.extensionId = extensionId;
32+
this.extensionName = extensionName;
33+
this.extensionRelativeEntryPointPath = extensionRelativeEntryPointPath;
34+
}
35+
36+
public long getMsId() {
37+
return msId;
38+
}
39+
40+
public long getExtensionId() {
41+
return extensionId;
42+
}
43+
44+
public String getExtensionName() {
45+
return extensionName;
46+
}
47+
48+
public String getExtensionRelativeEntryPointPath() {
49+
return extensionRelativeEntryPointPath;
50+
}
51+
52+
@Override
53+
public boolean executeInSequence() {
54+
return false;
55+
}
56+
}

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.cloudstack.framework.extensions.api.UnregisterExtensionCmd;
4343
import org.apache.cloudstack.framework.extensions.api.UpdateCustomActionCmd;
4444
import org.apache.cloudstack.framework.extensions.api.UpdateExtensionCmd;
45+
import org.apache.cloudstack.framework.extensions.command.GetExtensionEntryPointChecksumCommand;
4546

4647
import com.cloud.host.Host;
4748
import com.cloud.org.Cluster;
@@ -80,4 +81,6 @@ public interface ExtensionsManager extends Manager {
8081
ExtensionCustomActionResponse createCustomActionResponse(ExtensionCustomAction customAction);
8182

8283
Map<String, Object> getExternalAccessDetails(Host host);
84+
85+
String handleGetExtensionEntryPointChecksumCommand(GetExtensionEntryPointChecksumCommand cmd);
8386
}

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,21 @@
2626
import java.util.ArrayList;
2727
import java.util.Collection;
2828
import java.util.Collections;
29+
import java.util.Comparator;
2930
import java.util.EnumSet;
3031
import java.util.HashMap;
3132
import java.util.HashSet;
3233
import java.util.List;
3334
import java.util.Map;
3435
import java.util.Optional;
3536
import java.util.Set;
37+
import java.util.concurrent.Executors;
38+
import java.util.concurrent.ScheduledExecutorService;
39+
import java.util.concurrent.TimeUnit;
3640
import java.util.stream.Collectors;
3741

3842
import javax.inject.Inject;
43+
import javax.naming.ConfigurationException;
3944

4045
import org.apache.cloudstack.acl.RoleType;
4146
import org.apache.cloudstack.api.ApiConstants;
@@ -59,6 +64,7 @@
5964
import org.apache.cloudstack.framework.extensions.api.UnregisterExtensionCmd;
6065
import org.apache.cloudstack.framework.extensions.api.UpdateCustomActionCmd;
6166
import org.apache.cloudstack.framework.extensions.api.UpdateExtensionCmd;
67+
import org.apache.cloudstack.framework.extensions.command.GetExtensionEntryPointChecksumCommand;
6268
import org.apache.cloudstack.framework.extensions.dao.ExtensionCustomActionDao;
6369
import org.apache.cloudstack.framework.extensions.dao.ExtensionCustomActionDetailsDao;
6470
import org.apache.cloudstack.framework.extensions.dao.ExtensionDao;
@@ -71,6 +77,9 @@
7177
import org.apache.cloudstack.framework.extensions.vo.ExtensionResourceMapDetailsVO;
7278
import org.apache.cloudstack.framework.extensions.vo.ExtensionResourceMapVO;
7379
import org.apache.cloudstack.framework.extensions.vo.ExtensionVO;
80+
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
81+
import org.apache.cloudstack.management.ManagementServerHost;
82+
import org.apache.cloudstack.utils.identity.ManagementServerNode;
7483
import org.apache.commons.collections.CollectionUtils;
7584
import org.apache.commons.collections.MapUtils;
7685
import org.apache.commons.lang3.EnumUtils;
@@ -79,8 +88,13 @@
7988

8089
import com.cloud.agent.AgentManager;
8190
import com.cloud.agent.api.Answer;
91+
import com.cloud.agent.api.Command;
8292
import com.cloud.agent.api.RunCustomActionAnswer;
8393
import com.cloud.agent.api.RunCustomActionCommand;
94+
import com.cloud.alert.AlertManager;
95+
import com.cloud.cluster.ClusterManager;
96+
import com.cloud.cluster.ManagementServerHostVO;
97+
import com.cloud.cluster.dao.ManagementServerHostDao;
8498
import com.cloud.dc.ClusterVO;
8599
import com.cloud.dc.dao.ClusterDao;
86100
import com.cloud.event.ActionEvent;
@@ -95,11 +109,14 @@
95109
import com.cloud.hypervisor.ExternalProvisioner;
96110
import com.cloud.hypervisor.Hypervisor;
97111
import com.cloud.org.Cluster;
112+
import com.cloud.serializer.GsonHelper;
98113
import com.cloud.utils.Pair;
99114
import com.cloud.utils.component.ManagerBase;
100115
import com.cloud.utils.component.PluggableService;
116+
import com.cloud.utils.concurrency.NamedThreadFactory;
101117
import com.cloud.utils.db.EntityManager;
102118
import com.cloud.utils.db.Filter;
119+
import com.cloud.utils.db.GlobalLock;
103120
import com.cloud.utils.db.SearchBuilder;
104121
import com.cloud.utils.db.SearchCriteria;
105122
import com.cloud.utils.db.Transaction;
@@ -154,6 +171,17 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
154171
@Inject
155172
EntityManager entityManager;
156173

174+
@Inject
175+
ManagementServerHostDao managementServerHostDao;
176+
177+
@Inject
178+
ClusterManager clusterManager;
179+
180+
@Inject
181+
AlertManager alertManager;
182+
183+
private ScheduledExecutorService entryPointSyncCheckExecutor;
184+
157185
protected String getExtensionSafeName(String name) {
158186
return name.replaceAll("[^a-zA-Z0-9._-]", "_").toLowerCase();
159187
}
@@ -969,6 +997,37 @@ private Map<String, Object> getExternalAccessDetails(Map<String, String> actionD
969997
return externalDetails;
970998
}
971999

1000+
@Override
1001+
public String handleGetExtensionEntryPointChecksumCommand(GetExtensionEntryPointChecksumCommand cmd) {
1002+
final String extensionName = cmd.getExtensionName();
1003+
final String extensionRelativeEntryPointPath = cmd.getExtensionRelativeEntryPointPath();
1004+
logger.debug("Received GetExtensionEntryPointChecksumCommand from MS: {} for extension [id: {}, name: {}, relativeEntryPoint: {}]",
1005+
cmd.getMsId(), cmd.getExtensionId(), extensionName, extensionRelativeEntryPointPath);
1006+
return externalProvisioner.getChecksumForExtensionEntryPoint(extensionName, extensionRelativeEntryPointPath);
1007+
}
1008+
1009+
@Override
1010+
public boolean start() {
1011+
long syncCheckInitialDelay = 120;
1012+
long syncCheckInterval = 600;
1013+
logger.debug("Scheduling extensions entrypoint sync check task with initial delay={}s and interval={}s",
1014+
syncCheckInitialDelay, syncCheckInterval);
1015+
entryPointSyncCheckExecutor.scheduleWithFixedDelay(new EntryPointSyncCheckWorker(),
1016+
syncCheckInitialDelay, syncCheckInterval, TimeUnit.SECONDS);
1017+
return true;
1018+
}
1019+
1020+
@Override
1021+
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
1022+
try {
1023+
entryPointSyncCheckExecutor = Executors.newScheduledThreadPool(1,
1024+
new NamedThreadFactory("Extension-EntryPoint-Sync-Check"));
1025+
} catch (final Exception e) {
1026+
throw new ConfigurationException("Unable to to configure ExtensionsManagerImpl");
1027+
}
1028+
return true;
1029+
}
1030+
9721031
@Override
9731032
public List<Class<?>> getCommands() {
9741033
List<Class<?>> cmds = new ArrayList<>();
@@ -986,4 +1045,94 @@ public List<Class<?>> getCommands() {
9861045
cmds.add(UnregisterExtensionCmd.class);
9871046
return cmds;
9881047
}
1048+
1049+
public class EntryPointSyncCheckWorker extends ManagedContextRunnable {
1050+
protected void sendExtensionEntryPointOutOfSyncAlert(Extension extension) {
1051+
String msg = String.format("Entry-point for %s are out of sync across management servers",
1052+
extension);
1053+
alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_USERVM, 0L, 0L, msg, msg);
1054+
}
1055+
1056+
protected void updateExtensionSync(Extension extension, boolean sync) {
1057+
if (!sync) {
1058+
sendExtensionEntryPointOutOfSyncAlert(extension);
1059+
}
1060+
if (extension.isEntryPointSync() == sync) {
1061+
return;
1062+
}
1063+
ExtensionVO extensionVO = extensionDao.createForUpdate(extension.getId());
1064+
extensionVO.setEntryPointSync(sync);
1065+
extensionDao.update(extension.getId(), extensionVO);
1066+
}
1067+
1068+
protected String getChecksumFromMSPeer(Extension extension, ManagementServerHostVO msHost) {
1069+
final String msPeer = Long.toString(msHost.getMsid());
1070+
logger.debug("Retrieving checksum for {} from MS: {}", extension, msPeer);
1071+
final Command[] cmds = new Command[1];
1072+
cmds[0] = new GetExtensionEntryPointChecksumCommand(ManagementServerNode.getManagementServerId(),
1073+
extension.getId(), extension.getName(), extension.getRelativeEntryPoint());
1074+
return clusterManager.execute(msPeer, 0L, GsonHelper.getGson().toJson(cmds), true);
1075+
}
1076+
1077+
protected void checkSyncForOrchestrator(Extension extension, List<ManagementServerHostVO> msHosts) {
1078+
if (CollectionUtils.isEmpty(msHosts)) {
1079+
updateExtensionSync(extension, true);
1080+
return;
1081+
}
1082+
String checksum = externalProvisioner.getChecksumForExtensionEntryPoint(extension.getName(),
1083+
extension.getRelativeEntryPoint());
1084+
if (StringUtils.isBlank(checksum)) {
1085+
updateExtensionSync(extension, false);
1086+
return;
1087+
}
1088+
for (ManagementServerHostVO msHost : msHosts) {
1089+
final String msPeerChecksum = getChecksumFromMSPeer(extension, msHost);
1090+
if (!checksum.equals(msPeerChecksum)) {
1091+
logger.warn("Entry-point checksum for {} is different [msid: {}, checksum: {}] and [msid: {}, checksum: {}]",
1092+
extension, ManagementServerNode.getManagementServerId(), checksum,
1093+
msHost.getMsid(), msPeerChecksum);
1094+
updateExtensionSync(extension, false);
1095+
return;
1096+
}
1097+
}
1098+
updateExtensionSync(extension, true);
1099+
}
1100+
1101+
protected void runCleanupForLongestRunningManagementServer() {
1102+
try {
1103+
List<ManagementServerHostVO> msHosts = managementServerHostDao.listBy(ManagementServerHost.State.Up);
1104+
msHosts.sort(Comparator.comparingLong(ManagementServerHostVO::getRunid));
1105+
ManagementServerHostVO msHost = msHosts.remove(0);
1106+
if (msHost == null || (msHost.getMsid() != ManagementServerNode.getManagementServerId())) {
1107+
logger.debug("Skipping the extensions entrypoint sync check on this management server");
1108+
return;
1109+
}
1110+
List<ExtensionVO> extensions = extensionDao.listAll();
1111+
for (ExtensionVO extension : extensions) {
1112+
if (!Extension.Type.Orchestrator.equals(extension.getType())) {
1113+
continue;
1114+
}
1115+
checkSyncForOrchestrator(extension, msHosts);
1116+
}
1117+
} catch (Exception e) {
1118+
logger.warn("Cleanup task failed to cleanup old webhook deliveries", e);
1119+
}
1120+
}
1121+
1122+
@Override
1123+
protected void runInContext() {
1124+
GlobalLock gcLock = GlobalLock.getInternLock("ExtensionEntryPointSyncCheck");
1125+
try {
1126+
if (gcLock.lock(3)) {
1127+
try {
1128+
runCleanupForLongestRunningManagementServer();
1129+
} finally {
1130+
gcLock.unlock();
1131+
}
1132+
}
1133+
} finally {
1134+
gcLock.releaseRef();
1135+
}
1136+
}
1137+
}
9891138
}

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionVO.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public ExtensionVO(String name, String description, Type type, String relativeEn
7676
@Column(name = "relative_entry_point", nullable = false, length = 2048)
7777
private String relativeEntryPoint;
7878

79+
@Column(name = "entry_point_sync")
80+
private boolean entryPointSync;
81+
7982
@Column(name = "is_user_defined")
8083
private boolean userDefined;
8184

@@ -127,6 +130,15 @@ public void setRelativeEntryPoint(String relativeEntryPoint) {
127130
this.relativeEntryPoint = relativeEntryPoint;
128131
}
129132

133+
@Override
134+
public boolean isEntryPointSync() {
135+
return entryPointSync;
136+
}
137+
138+
public void setEntryPointSync(boolean entryPointSync) {
139+
this.entryPointSync = entryPointSync;
140+
}
141+
130142
@Override
131143
public boolean isUserDefined() {
132144
return userDefined;

0 commit comments

Comments
 (0)