Skip to content

Commit a19754f

Browse files
author
Zhang Wenhao
committed
<feature>[kvm]: support clone for TPM VM
* Added support for cross-VM cloning of host files (NvRam, TpmState) and TPM encryption key cloning * KvmSecureBootManager transitioned from a Component to a service * Introducing new message/response types, workflows, and TPM key backend interfaces along with their virtual implementations Resolves: ZSV-11439 Related: ZSV-11310 Change-Id: I796b77716b6c64657469697a7a797a7268636e6e
1 parent cda3369 commit a19754f

11 files changed

Lines changed: 373 additions & 2 deletions

File tree

conf/springConfigXml/Kvm.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
<bean id="KvmSecureBootManager" class="org.zstack.kvm.efi.KvmSecureBootManager">
263263
<zstack:plugin>
264264
<zstack:extension interface="org.zstack.header.Component"/>
265+
<zstack:extension interface="org.zstack.header.Service" />
265266
</zstack:plugin>
266267
</bean>
267268

@@ -271,4 +272,6 @@
271272
<zstack:extension interface="org.zstack.header.vm.PreVmInstantiateResourceExtensionPoint" />
272273
</zstack:plugin>
273274
</bean>
275+
276+
<bean id="DummyTpmEncryptedResourceKeyBackend" class="org.zstack.kvm.tpm.DummyTpmEncryptedResourceKeyBackend"/>
274277
</beans>

header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@PythonClass
77
public interface VmInstanceConstant {
88
String SERVICE_ID = "vmInstance";
9+
String SECURE_BOOT_SERVICE_ID = "secureBoot";
910
String ACTION_CATEGORY = "instance";
1011
@PythonClass
1112
String USER_VM_TYPE = "UserVm";
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.zstack.kvm.efi;
2+
3+
import org.zstack.header.message.NeedReplyMessage;
4+
5+
import java.util.List;
6+
7+
public class CloneVmHostFileMsg extends NeedReplyMessage {
8+
private String srcVmUuid;
9+
private List<String> dstVmUuidList;
10+
private Boolean resetTpm;
11+
12+
public String getSrcVmUuid() {
13+
return srcVmUuid;
14+
}
15+
16+
public void setSrcVmUuid(String srcVmUuid) {
17+
this.srcVmUuid = srcVmUuid;
18+
}
19+
20+
public List<String> getDstVmUuidList() {
21+
return dstVmUuidList;
22+
}
23+
24+
public void setDstVmUuidList(List<String> dstVmUuidList) {
25+
this.dstVmUuidList = dstVmUuidList;
26+
}
27+
28+
public Boolean getResetTpm() {
29+
return resetTpm;
30+
}
31+
32+
public void setResetTpm(Boolean resetTpm) {
33+
this.resetTpm = resetTpm;
34+
}
35+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.zstack.kvm.efi;
2+
3+
import org.zstack.header.message.MessageReply;
4+
5+
public class CloneVmHostFileReply extends MessageReply {
6+
}

plugin/kvm/src/main/java/org/zstack/kvm/efi/KvmSecureBootManager.java

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,66 @@
22

33
import org.springframework.beans.factory.annotation.Autowired;
44
import org.zstack.compute.legacy.ComputeLegacyGlobalProperty;
5+
import org.zstack.core.Platform;
6+
import org.zstack.core.asyncbatch.While;
7+
import org.zstack.core.cloudbus.CloudBus;
58
import org.zstack.core.cloudbus.EventCallback;
69
import org.zstack.core.cloudbus.EventFacadeImpl;
10+
import org.zstack.core.db.DatabaseFacade;
711
import org.zstack.core.db.Q;
8-
import org.zstack.header.Component;
12+
import org.zstack.core.workflow.SimpleFlowChain;
13+
import org.zstack.header.AbstractService;
914
import org.zstack.header.core.Completion;
15+
import org.zstack.header.core.WhileDoneCompletion;
16+
import org.zstack.header.core.workflow.FlowDoneHandler;
17+
import org.zstack.header.core.workflow.FlowErrorHandler;
18+
import org.zstack.header.core.workflow.FlowTrigger;
19+
import org.zstack.header.core.workflow.NoRollbackFlow;
1020
import org.zstack.header.errorcode.ErrorCode;
21+
import org.zstack.header.errorcode.ErrorCodeList;
22+
import org.zstack.header.exception.CloudRuntimeException;
23+
import org.zstack.header.message.Message;
1124
import org.zstack.header.vm.VmCanonicalEvents;
25+
import org.zstack.header.vm.VmInstanceConstant;
1226
import org.zstack.header.vm.VmInstanceVO;
1327
import org.zstack.header.vm.VmInstanceVO_;
28+
import org.zstack.header.vm.additions.VmHostBackupFileVO;
29+
import org.zstack.header.vm.additions.VmHostFileContentVO;
30+
import org.zstack.header.vm.additions.VmHostFileContentVO_;
1431
import org.zstack.header.vm.additions.VmHostFileType;
1532
import org.zstack.header.vm.additions.VmHostFileVO;
1633
import org.zstack.header.vm.additions.VmHostFileVO_;
34+
import org.zstack.resourceconfig.ResourceConfig;
35+
import org.zstack.resourceconfig.ResourceConfigFacade;
36+
import org.zstack.utils.DebugUtils;
1737
import org.zstack.utils.Utils;
1838
import org.zstack.utils.logging.CLogger;
1939

2040
import javax.persistence.Tuple;
41+
import java.sql.Timestamp;
42+
import java.time.Instant;
43+
import java.util.ArrayList;
2144
import java.util.List;
2245
import java.util.Map;
2346

47+
import static org.zstack.compute.vm.VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE;
48+
import static org.zstack.kvm.efi.KvmSecureBootExtensions.*;
2449
import static org.zstack.utils.CollectionDSL.list;
2550
import static org.zstack.utils.CollectionUtils.findOneOrNull;
51+
import static org.zstack.utils.CollectionUtils.transform;
2652

27-
public class KvmSecureBootManager implements Component {
53+
public class KvmSecureBootManager extends AbstractService {
2854
private static final CLogger logger = Utils.getLogger(KvmSecureBootManager.class);
2955

56+
@Autowired
57+
private CloudBus bus;
58+
@Autowired
59+
private DatabaseFacade databaseFacade;
3060
@Autowired
3161
private EventFacadeImpl eventFacade;
3262
@Autowired
63+
private ResourceConfigFacade resourceConfigFacade;
64+
@Autowired
3365
private KvmSecureBootExtensions secureBootExtensions;
3466

3567
@Override
@@ -101,4 +133,181 @@ public void fail(ErrorCode errorCode) {
101133
}
102134
});
103135
}
136+
137+
@Override
138+
public String getId() {
139+
return bus.makeLocalServiceId(VmInstanceConstant.SECURE_BOOT_SERVICE_ID);
140+
}
141+
142+
@Override
143+
public void handleMessage(Message msg) {
144+
if (msg instanceof CloneVmHostFileMsg) {
145+
handle((CloneVmHostFileMsg) msg);
146+
} else {
147+
bus.dealWithUnknownMessage(msg);
148+
}
149+
}
150+
151+
@SuppressWarnings("rawtypes")
152+
private void handle(CloneVmHostFileMsg msg) {
153+
CloneVmHostFileReply reply = new CloneVmHostFileReply();
154+
List<VmHostFileType> needClone = list(VmHostFileType.NvRam);
155+
156+
boolean resetTpm;
157+
if (msg.getResetTpm() == null) {
158+
ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity());
159+
resetTpm = resourceConfig.getResourceConfigValue(msg.getSrcVmUuid(), Boolean.class);
160+
} else {
161+
resetTpm = msg.getResetTpm();
162+
}
163+
if (resetTpm) {
164+
needClone.add(VmHostFileType.TpmState);
165+
}
166+
167+
List<VmHostFileVO> files = new ArrayList<>();
168+
List<SyncVmHostFilesFromHostContext> syncContexts = new ArrayList<>();
169+
170+
SimpleFlowChain chain = new SimpleFlowChain();
171+
chain.setName("clone-vm-host-file");
172+
chain.then(new NoRollbackFlow() {
173+
String __name__ = "prepare-sync-vm-host-file-context-list";
174+
175+
@Override
176+
public void run(FlowTrigger trigger, Map data) {
177+
for (VmHostFileType type : needClone) {
178+
VmHostFileVO file = Q.New(VmHostFileVO.class)
179+
.eq(VmHostFileVO_.vmInstanceUuid, msg.getSrcVmUuid())
180+
.eq(VmHostFileVO_.type, type)
181+
.orderByDesc(VmHostFileVO_.lastOpDate)
182+
.limit(1)
183+
.find();
184+
if (file == null) {
185+
logger.debug(String.format("skip to read/write %s host file for VM[vmUuid=%s]: file is not registered in MN",
186+
type, msg.getSrcVmUuid()));
187+
continue;
188+
}
189+
files.add(file);
190+
}
191+
192+
if (files.isEmpty()) {
193+
trigger.next();
194+
return;
195+
}
196+
197+
List<String> hostUuids = transform(files, VmHostFileVO::getHostUuid);
198+
for (String hostUuid : hostUuids) {
199+
SyncVmHostFilesFromHostContext syncContext = new SyncVmHostFilesFromHostContext();
200+
syncContext.hostUuid = hostUuid;
201+
syncContext.vmUuid = msg.getSrcVmUuid();
202+
syncContexts.add(syncContext);
203+
}
204+
205+
for (VmHostFileVO file : files) {
206+
SyncVmHostFilesFromHostContext syncContext = findOneOrNull(syncContexts,
207+
item -> file.getHostUuid().equals(item.hostUuid));
208+
DebugUtils.Assert(syncContext != null, "syncContext must be not null");
209+
if (file.getType() == VmHostFileType.NvRam) {
210+
syncContext.nvRamPath = file.getPath();
211+
} else if (file.getType() == VmHostFileType.TpmState) {
212+
syncContext.tpmStateFolder = file.getPath();
213+
} else {
214+
throw new CloudRuntimeException("unsupported vm host file type: " + file.getType());
215+
}
216+
}
217+
218+
trigger.next();
219+
}
220+
}).then(new NoRollbackFlow() {
221+
String __name__ = "read-vm-host-file-from-origin-host";
222+
223+
@Override
224+
public void run(FlowTrigger trigger, Map data) {
225+
new While<>(syncContexts).each((syncContext, whileContext) ->
226+
secureBootExtensions.syncVmHostFilesFromHost(syncContext, new Completion(whileContext) {
227+
@Override
228+
public void success() {
229+
whileContext.done();
230+
}
231+
232+
@Override
233+
public void fail(ErrorCode errorCode) {
234+
whileContext.addError(errorCode);
235+
whileContext.done();
236+
}
237+
})
238+
).run(new WhileDoneCompletion(trigger) {
239+
@Override
240+
public void done(ErrorCodeList errorCodeList) {
241+
if (!errorCodeList.isEmpty()) {
242+
logger.warn(String.format("failed to sync host file for VM[uuid=%s] but still continue:\n%s",
243+
msg.getSrcVmUuid(),
244+
String.join("\n", transform(errorCodeList.getCauses(), ErrorCode::getReadableDetails))));
245+
}
246+
trigger.next();
247+
}
248+
});
249+
}
250+
}).then(new NoRollbackFlow() {
251+
String __name__ = "copy-host-content-database";
252+
253+
@Override
254+
public void run(FlowTrigger trigger, Map data) {
255+
List<String> uuidList = transform(files, VmHostFileVO::getUuid);
256+
List<VmHostFileVO> filesAfterSyncing = Q.New(VmHostFileVO.class)
257+
.in(VmHostFileVO_.uuid, uuidList)
258+
.list();
259+
List<VmHostFileContentVO> contents = Q.New(VmHostFileContentVO.class)
260+
.in(VmHostFileContentVO_.uuid, uuidList)
261+
.list();
262+
263+
List<VmHostBackupFileVO> filesNeedPersists = new ArrayList<>();
264+
List<VmHostFileContentVO> contentsNeedPersists = new ArrayList<>();
265+
266+
Timestamp now = Timestamp.from(Instant.now());
267+
for (String vmUuid : msg.getDstVmUuidList()) {
268+
for (VmHostFileVO vmHostFileVO : filesAfterSyncing) {
269+
VmHostBackupFileVO file = new VmHostBackupFileVO();
270+
file.setUuid(Platform.getUuid());
271+
file.setVmInstanceUuid(vmUuid);
272+
file.setType(vmHostFileVO.getType());
273+
file.setCreateDate(now);
274+
file.setLastOpDate(now);
275+
filesNeedPersists.add(file);
276+
277+
VmHostFileContentVO srcContent = findOneOrNull(contents,
278+
item -> item.getUuid().equals(vmHostFileVO.getUuid()));
279+
if (srcContent == null) {
280+
continue;
281+
}
282+
VmHostFileContentVO content = new VmHostFileContentVO();
283+
content.setUuid(file.getUuid());
284+
content.setContent(srcContent.getContent());
285+
content.setFormat(srcContent.getFormat());
286+
content.setCreateDate(now);
287+
content.setLastOpDate(now);
288+
contentsNeedPersists.add(content);
289+
}
290+
}
291+
292+
if (!filesNeedPersists.isEmpty()) {
293+
databaseFacade.persistCollection(filesNeedPersists);
294+
}
295+
if (!contentsNeedPersists.isEmpty()) {
296+
databaseFacade.persistCollection(contentsNeedPersists);
297+
}
298+
trigger.next();
299+
}
300+
}).done(new FlowDoneHandler(msg) {
301+
@Override
302+
public void handle(Map data) {
303+
bus.reply(msg, reply);
304+
}
305+
}).error(new FlowErrorHandler(msg) {
306+
@Override
307+
public void handle(ErrorCode errCode, Map data) {
308+
reply.setError(errCode);
309+
bus.reply(msg, reply);
310+
}
311+
}).start();
312+
}
104313
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.zstack.kvm.tpm;
2+
3+
import org.zstack.header.message.NeedReplyMessage;
4+
5+
public class CloneVmTpmMsg extends NeedReplyMessage {
6+
private String srcVmUuid;
7+
private String dstVmUuid;
8+
private Boolean resetTpm;
9+
10+
public String getSrcVmUuid() {
11+
return srcVmUuid;
12+
}
13+
14+
public void setSrcVmUuid(String srcVmUuid) {
15+
this.srcVmUuid = srcVmUuid;
16+
}
17+
18+
public String getDstVmUuid() {
19+
return dstVmUuid;
20+
}
21+
22+
public void setDstVmUuid(String dstVmUuid) {
23+
this.dstVmUuid = dstVmUuid;
24+
}
25+
26+
public Boolean getResetTpm() {
27+
return resetTpm;
28+
}
29+
30+
public void setResetTpm(Boolean resetTpm) {
31+
this.resetTpm = resetTpm;
32+
}
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.zstack.kvm.tpm;
2+
3+
import org.zstack.header.message.MessageReply;
4+
5+
public class CloneVmTpmReply extends MessageReply {
6+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.zstack.kvm.tpm;
2+
3+
import org.zstack.header.core.Completion;
4+
import org.zstack.utils.Utils;
5+
import org.zstack.utils.logging.CLogger;
6+
7+
public class DummyTpmEncryptedResourceKeyBackend implements TpmEncryptedResourceKeyBackend {
8+
private static final CLogger logger = Utils.getLogger(DummyTpmEncryptedResourceKeyBackend.class);
9+
10+
@Override
11+
public void cloneEncryptedResourceKey(CloneEncryptedResourceKeyContext context, Completion completion) {
12+
// do nothing
13+
logger.debug("ignore clone encrypted resource key request for TPM uuid "
14+
+ context.srcTpmUuid + " -> " + context.dstTpmUuid);
15+
completion.success();
16+
}
17+
}

0 commit comments

Comments
 (0)