Skip to content

Commit b2c1689

Browse files
author
Zhang Wenhao
committed
<feature>[kvm]: support clone for TPM Vm
Resolves: ZSV-11439 Related: ZSV-11310 Change-Id: I796b77716b6c64657469697a7a797a7268636e6e
1 parent 3031fbd commit b2c1689

13 files changed

Lines changed: 372 additions & 4 deletions

File tree

conf/db/zsv/V5.0.0__schema.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ CREATE TABLE IF NOT EXISTS `zstack`.`TpmVO` (
1212
CREATE TABLE IF NOT EXISTS `zstack`.`VmHostFileVO` (
1313
`uuid` char(32) NOT NULL UNIQUE,
1414
`vmInstanceUuid` char(32) NOT NULL,
15-
`hostUuid` char(32) NOT NULL,
15+
`hostUuid` char(32) DEFAULT NULL COMMENT 'null if type is NvRamBackup or TpmStateBackup',
1616
`type` varchar(64) NOT NULL COMMENT 'NvRam, TpmState, NvRamBackup, TpmStateBackup',
17-
`path` varchar(1024) NOT NULL COMMENT 'Absolute path of the file on the host',
17+
`path` varchar(1024) DEFAULT NULL COMMENT 'null if type is NvRamBackup or TpmStateBackup',
1818
`lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
1919
`createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59',
2020
PRIMARY KEY (`uuid`),

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";

header/src/main/java/org/zstack/header/vm/additions/VmHostFileType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,16 @@ public enum VmHostFileType {
55
TpmState,
66
NvRamBackup,
77
TpmStateBackup,
8+
;
9+
10+
public static VmHostFileType backupType(VmHostFileType type) {
11+
switch (type) {
12+
case NvRam: case NvRamBackup:
13+
return NvRamBackup;
14+
case TpmState: case TpmStateBackup:
15+
return TpmStateBackup;
16+
default:
17+
return null;
18+
}
19+
}
820
}
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: 196 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,63 @@
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.FlowTrigger;
17+
import org.zstack.header.core.workflow.NoRollbackFlow;
1018
import org.zstack.header.errorcode.ErrorCode;
19+
import org.zstack.header.errorcode.ErrorCodeList;
20+
import org.zstack.header.exception.CloudRuntimeException;
21+
import org.zstack.header.message.Message;
1122
import org.zstack.header.vm.VmCanonicalEvents;
23+
import org.zstack.header.vm.VmInstanceConstant;
1224
import org.zstack.header.vm.VmInstanceVO;
1325
import org.zstack.header.vm.VmInstanceVO_;
26+
import org.zstack.header.vm.additions.VmHostFileContentVO;
27+
import org.zstack.header.vm.additions.VmHostFileContentVO_;
1428
import org.zstack.header.vm.additions.VmHostFileType;
1529
import org.zstack.header.vm.additions.VmHostFileVO;
1630
import org.zstack.header.vm.additions.VmHostFileVO_;
31+
import org.zstack.resourceconfig.ResourceConfig;
32+
import org.zstack.resourceconfig.ResourceConfigFacade;
33+
import org.zstack.utils.DebugUtils;
1734
import org.zstack.utils.Utils;
1835
import org.zstack.utils.logging.CLogger;
1936

2037
import javax.persistence.Tuple;
38+
import java.sql.Timestamp;
39+
import java.time.Instant;
40+
import java.util.ArrayList;
2141
import java.util.List;
2242
import java.util.Map;
2343

44+
import static org.zstack.compute.vm.VmGlobalConfig.RESET_TPM_AFTER_VM_CLONE;
45+
import static org.zstack.kvm.efi.KvmSecureBootExtensions.*;
2446
import static org.zstack.utils.CollectionDSL.list;
2547
import static org.zstack.utils.CollectionUtils.findOneOrNull;
48+
import static org.zstack.utils.CollectionUtils.transform;
2649

27-
public class KvmSecureBootManager implements Component {
50+
public class KvmSecureBootManager extends AbstractService {
2851
private static final CLogger logger = Utils.getLogger(KvmSecureBootManager.class);
2952

53+
@Autowired
54+
private CloudBus bus;
55+
@Autowired
56+
private DatabaseFacade databaseFacade;
3057
@Autowired
3158
private EventFacadeImpl eventFacade;
3259
@Autowired
60+
private ResourceConfigFacade resourceConfigFacade;
61+
@Autowired
3362
private KvmSecureBootExtensions secureBootExtensions;
3463

3564
@Override
@@ -101,4 +130,169 @@ public void fail(ErrorCode errorCode) {
101130
}
102131
});
103132
}
133+
134+
@Override
135+
public String getId() {
136+
return bus.makeLocalServiceId(VmInstanceConstant.SECURE_BOOT_SERVICE_ID);
137+
}
138+
139+
@Override
140+
public void handleMessage(Message msg) {
141+
if (msg instanceof CloneVmHostFileMsg) {
142+
handle((CloneVmHostFileMsg) msg);
143+
} else {
144+
bus.dealWithUnknownMessage(msg);
145+
}
146+
}
147+
148+
@SuppressWarnings("rawtypes")
149+
private void handle(CloneVmHostFileMsg msg) {
150+
List<VmHostFileType> needClone = list(VmHostFileType.NvRam);
151+
152+
boolean resetTpm;
153+
if (msg.getResetTpm() == null) {
154+
ResourceConfig resourceConfig = resourceConfigFacade.getResourceConfig(RESET_TPM_AFTER_VM_CLONE.getIdentity());
155+
resetTpm = resourceConfig.getResourceConfigValue(msg.getSrcVmUuid(), Boolean.class);
156+
} else {
157+
resetTpm = msg.getResetTpm();
158+
}
159+
if (resetTpm) {
160+
needClone.add(VmHostFileType.TpmState);
161+
}
162+
163+
List<VmHostFileVO> files = new ArrayList<>();
164+
List<SyncVmHostFilesFromHostContext> syncContexts = new ArrayList<>();
165+
166+
SimpleFlowChain chain = new SimpleFlowChain();
167+
chain.setName("clone-vm-host-file");
168+
chain.then(new NoRollbackFlow() {
169+
String __name__ = "prepare-sync-vm-host-file-context-list";
170+
171+
@Override
172+
public void run(FlowTrigger trigger, Map data) {
173+
for (VmHostFileType type : needClone) {
174+
VmHostFileVO file = Q.New(VmHostFileVO.class)
175+
.eq(VmHostFileVO_.vmInstanceUuid, msg.getSrcVmUuid())
176+
.eq(VmHostFileVO_.type, type)
177+
.orderByDesc(VmHostFileVO_.lastOpDate)
178+
.limit(1)
179+
.find();
180+
if (file == null) {
181+
logger.debug(String.format("skip to read/write %s host file for VM[vmUuid=%s]: file is not registered in MN",
182+
type, msg.getSrcVmUuid()));
183+
continue;
184+
}
185+
files.add(file);
186+
}
187+
188+
if (files.isEmpty()) {
189+
trigger.next();
190+
return;
191+
}
192+
193+
List<String> hostUuids = transform(files, VmHostFileVO::getHostUuid);
194+
for (String hostUuid : hostUuids) {
195+
SyncVmHostFilesFromHostContext syncContext = new SyncVmHostFilesFromHostContext();
196+
syncContext.hostUuid = hostUuid;
197+
syncContext.vmUuid = msg.getSrcVmUuid();
198+
syncContexts.add(syncContext);
199+
}
200+
201+
for (VmHostFileVO file : files) {
202+
SyncVmHostFilesFromHostContext syncContext = findOneOrNull(syncContexts,
203+
item -> file.getHostUuid().equals(item.hostUuid));
204+
DebugUtils.Assert(syncContext != null, "syncContext must be not null");
205+
if (file.getType() == VmHostFileType.NvRam) {
206+
syncContext.nvRamPath = file.getPath();
207+
} else if (file.getType() == VmHostFileType.TpmState) {
208+
syncContext.tpmStateFolder = file.getPath();
209+
} else {
210+
throw new CloudRuntimeException("unsupported vm host file type: " + file.getType());
211+
}
212+
}
213+
214+
trigger.next();
215+
}
216+
}).then(new NoRollbackFlow() {
217+
String __name__ = "read-vm-host-file-from-origin-host";
218+
219+
@Override
220+
public void run(FlowTrigger trigger, Map data) {
221+
new While<>(syncContexts).each((syncContext, whileContext) ->
222+
secureBootExtensions.syncVmHostFilesFromHost(syncContext, new Completion(whileContext) {
223+
@Override
224+
public void success() {
225+
whileContext.done();
226+
}
227+
228+
@Override
229+
public void fail(ErrorCode errorCode) {
230+
whileContext.addError(errorCode);
231+
whileContext.done();
232+
}
233+
})
234+
).run(new WhileDoneCompletion(trigger) {
235+
@Override
236+
public void done(ErrorCodeList errorCodeList) {
237+
logger.warn(String.format("failed to sync host file for VM[uuid=%s] but still continue:\n%s",
238+
msg.getSrcVmUuid(),
239+
String.join("\n", transform(errorCodeList.getCauses(), ErrorCode::getReadableDetails))));
240+
trigger.next();
241+
}
242+
});
243+
}
244+
}).then(new NoRollbackFlow() {
245+
String __name__ = "copy-host-content-database";
246+
247+
@Override
248+
public void run(FlowTrigger trigger, Map data) {
249+
List<String> uuidList = transform(files, VmHostFileVO::getUuid);
250+
List<VmHostFileVO> filesAfterSyncing = Q.New(VmHostFileVO.class)
251+
.in(VmHostFileVO_.uuid, uuidList)
252+
.list();
253+
List<VmHostFileContentVO> contents = Q.New(VmHostFileContentVO.class)
254+
.in(VmHostFileContentVO_.uuid, uuidList)
255+
.list();
256+
257+
List<VmHostFileVO> filesNeedPersists = new ArrayList<>();
258+
List<VmHostFileContentVO> contentsNeedPersists = new ArrayList<>();
259+
260+
Timestamp now = Timestamp.from(Instant.now());
261+
for (String vmUuid : msg.getDstVmUuidList()) {
262+
for (VmHostFileVO vmHostFileVO : filesAfterSyncing) {
263+
VmHostFileVO file = new VmHostFileVO();
264+
file.setUuid(Platform.getUuid());
265+
file.setVmInstanceUuid(vmUuid);
266+
file.setHostUuid(null);
267+
file.setType(VmHostFileType.backupType(vmHostFileVO.getType()));
268+
file.setPath(null);
269+
file.setCreateDate(now);
270+
file.setLastOpDate(now);
271+
filesNeedPersists.add(file);
272+
273+
VmHostFileContentVO srcContent = findOneOrNull(contents,
274+
item -> item.getUuid().equals(vmHostFileVO.getUuid()));
275+
if (srcContent == null) {
276+
continue;
277+
}
278+
VmHostFileContentVO content = new VmHostFileContentVO();
279+
content.setUuid(file.getUuid());
280+
content.setContent(srcContent.getContent());
281+
content.setFormat(srcContent.getFormat());
282+
content.setCreateDate(now);
283+
content.setLastOpDate(now);
284+
contentsNeedPersists.add(content);
285+
}
286+
}
287+
288+
if (!filesNeedPersists.isEmpty()) {
289+
databaseFacade.persistCollection(filesNeedPersists);
290+
}
291+
if (!contentsNeedPersists.isEmpty()) {
292+
databaseFacade.persistCollection(contentsNeedPersists);
293+
}
294+
trigger.next();
295+
}
296+
});
297+
}
104298
}
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)