/
UpdateVmCommand.java
1782 lines (1534 loc) · 78.7 KB
/
UpdateVmCommand.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.ovirt.engine.core.bll;
import static org.ovirt.engine.core.bll.validator.CpuPinningValidator.isCpuPinningValid;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Typed;
import javax.inject.Inject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.bll.context.CommandContext;
import org.ovirt.engine.core.bll.memory.MemoryUtils;
import org.ovirt.engine.core.bll.network.cluster.NetworkHelper;
import org.ovirt.engine.core.bll.quota.QuotaClusterConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaConsumptionParameter;
import org.ovirt.engine.core.bll.quota.QuotaSanityParameter;
import org.ovirt.engine.core.bll.quota.QuotaVdsDependent;
import org.ovirt.engine.core.bll.scheduling.SlaValidator;
import org.ovirt.engine.core.bll.scheduling.utils.VdsCpuUnitPinningHelper;
import org.ovirt.engine.core.bll.snapshots.SnapshotVmConfigurationHelper;
import org.ovirt.engine.core.bll.storage.domain.IsoDomainListSynchronizer;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.bll.utils.IconUtils;
import org.ovirt.engine.core.bll.utils.PermissionSubject;
import org.ovirt.engine.core.bll.validator.AffinityValidator;
import org.ovirt.engine.core.bll.validator.IconValidator;
import org.ovirt.engine.core.bll.validator.InClusterUpgradeValidator;
import org.ovirt.engine.core.bll.validator.VmValidator;
import org.ovirt.engine.core.bll.validator.VmWatchdogValidator;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.FeatureSupported;
import org.ovirt.engine.core.common.VdcObjectType;
import org.ovirt.engine.core.common.action.ActionReturnValue;
import org.ovirt.engine.core.common.action.ActionType;
import org.ovirt.engine.core.common.action.GraphicsParameters;
import org.ovirt.engine.core.common.action.HotSetAmountOfMemoryParameters;
import org.ovirt.engine.core.common.action.HotSetNumberOfCpusParameters;
import org.ovirt.engine.core.common.action.HotUnplugMemoryWithoutVmUpdateParameters;
import org.ovirt.engine.core.common.action.LockProperties;
import org.ovirt.engine.core.common.action.LockProperties.Scope;
import org.ovirt.engine.core.common.action.PlugAction;
import org.ovirt.engine.core.common.action.RngDeviceParameters;
import org.ovirt.engine.core.common.action.UpdateVmVersionParameters;
import org.ovirt.engine.core.common.action.VmManagementParametersBase;
import org.ovirt.engine.core.common.action.VmNumaNodeOperationParameters;
import org.ovirt.engine.core.common.action.WatchdogParameters;
import org.ovirt.engine.core.common.businessentities.ActionGroup;
import org.ovirt.engine.core.common.businessentities.ArchitectureType;
import org.ovirt.engine.core.common.businessentities.BiosType;
import org.ovirt.engine.core.common.businessentities.DisplayType;
import org.ovirt.engine.core.common.businessentities.GraphicsDevice;
import org.ovirt.engine.core.common.businessentities.GraphicsType;
import org.ovirt.engine.core.common.businessentities.Label;
import org.ovirt.engine.core.common.businessentities.MigrationSupport;
import org.ovirt.engine.core.common.businessentities.Provider;
import org.ovirt.engine.core.common.businessentities.ProviderType;
import org.ovirt.engine.core.common.businessentities.UsbPolicy;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VMStatus;
import org.ovirt.engine.core.common.businessentities.VmBase;
import org.ovirt.engine.core.common.businessentities.VmDevice;
import org.ovirt.engine.core.common.businessentities.VmDeviceGeneralType;
import org.ovirt.engine.core.common.businessentities.VmDeviceId;
import org.ovirt.engine.core.common.businessentities.VmInit;
import org.ovirt.engine.core.common.businessentities.VmNumaNode;
import org.ovirt.engine.core.common.businessentities.VmPayload;
import org.ovirt.engine.core.common.businessentities.VmPool;
import org.ovirt.engine.core.common.businessentities.VmRngDevice;
import org.ovirt.engine.core.common.businessentities.VmStatic;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.VmType;
import org.ovirt.engine.core.common.businessentities.VmWatchdog;
import org.ovirt.engine.core.common.businessentities.network.Network;
import org.ovirt.engine.core.common.businessentities.network.VmNic;
import org.ovirt.engine.core.common.businessentities.storage.DiskVmElement;
import org.ovirt.engine.core.common.businessentities.storage.ImageFileType;
import org.ovirt.engine.core.common.businessentities.storage.RepoImage;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.errors.EngineMessage;
import org.ovirt.engine.core.common.locks.LockingGroup;
import org.ovirt.engine.core.common.queries.IdQueryParameters;
import org.ovirt.engine.core.common.queries.QueryReturnValue;
import org.ovirt.engine.core.common.queries.QueryType;
import org.ovirt.engine.core.common.scheduling.AffinityGroup;
import org.ovirt.engine.core.common.utils.HugePageUtils;
import org.ovirt.engine.core.common.utils.MDevTypesUtils;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.utils.VmCommonUtils;
import org.ovirt.engine.core.common.utils.VmCpuCountHelper;
import org.ovirt.engine.core.common.utils.VmDeviceCommonUtils;
import org.ovirt.engine.core.common.utils.VmDeviceType;
import org.ovirt.engine.core.common.utils.customprop.VmPropertiesUtils;
import org.ovirt.engine.core.common.validation.group.UpdateVm;
import org.ovirt.engine.core.common.vdscommands.LeaseVDSParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.compat.DateTime;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl;
import org.ovirt.engine.core.dao.DiskImageDao;
import org.ovirt.engine.core.dao.DiskVmElementDao;
import org.ovirt.engine.core.dao.LabelDao;
import org.ovirt.engine.core.dao.VmDeviceDao;
import org.ovirt.engine.core.dao.VmDynamicDao;
import org.ovirt.engine.core.dao.VmInitDao;
import org.ovirt.engine.core.dao.VmNumaNodeDao;
import org.ovirt.engine.core.dao.VmPoolDao;
import org.ovirt.engine.core.dao.VmStaticDao;
import org.ovirt.engine.core.dao.VmTemplateDao;
import org.ovirt.engine.core.dao.network.NetworkDao;
import org.ovirt.engine.core.dao.network.VmNicDao;
import org.ovirt.engine.core.dao.provider.ProviderDao;
import org.ovirt.engine.core.dao.scheduling.AffinityGroupDao;
import org.ovirt.engine.core.utils.ReplacementUtils;
import org.ovirt.engine.core.vdsbroker.ResourceManager;
import org.ovirt.engine.core.vdsbroker.vdsbroker.CloudInitHandler;
public class UpdateVmCommand<T extends VmManagementParametersBase> extends VmManagementCommandBase<T>
implements QuotaVdsDependent, RenamedEntityInfoProvider{
private static final Base64 BASE_64 = new Base64(0, null);
private static final String AUDIT_LOG_MEMORY_HOT_UNPLUG_OPTIONS = "memoryHotUnplugOptions";
private static final String AUDIT_LOG_OLD_MEMORY_MB = "oldMemoryMb";
private static final String AUDIT_LOG_NEW_MEMORY_MB = "newMemoryMB";
@Inject
private AuditLogDirector auditLogDirector;
@Inject
private VmSlaPolicyUtils vmSlaPolicyUtils;
@Inject
private ResourceManager resourceManager;
@Inject
private InClusterUpgradeValidator clusterUpgradeValidator;
@Inject
private IsoDomainListSynchronizer isoDomainListSynchronizer;
@Inject
private DiskImageDao diskImageDao;
@Inject
private VmNumaNodeDao vmNumaNodeDao;
@Inject
private VmStaticDao vmStaticDao;
@Inject
private VmDynamicDao vmDynamicDao;
@Inject
private VmDeviceDao vmDeviceDao;
@Inject
private NetworkDao networkDao;
@Inject
private VmNicDao vmNicDao;
@Inject
private ProviderDao providerDao;
@Inject
private VmPoolDao vmPoolDao;
@Inject
private VmInitDao vmInitDao;
@Inject
private DiskVmElementDao diskVmElementDao;
@Inject
private VmTemplateDao vmTemplateDao;
@Inject
private AffinityGroupDao affinityGroupDao;
@Inject
private LabelDao labelDao;
@Inject
private NetworkHelper networkHelper;
@Inject
private IconUtils iconUtils;
@Inject
private CloudInitHandler cloudInitHandler;
@Inject
@Typed(ConcurrentChildCommandsExecutionCallback.class)
private Instance<ConcurrentChildCommandsExecutionCallback> callbackProvider;
@Inject
private AffinityValidator affinityValidator;
@Inject
private SnapshotVmConfigurationHelper snapshotVmConfigurationHelper;
@Inject
private VdsCpuUnitPinningHelper vdsCpuUnitPinningHelper;
private VM oldVm;
private boolean quotaSanityOnly = false;
private VmStatic newVmStatic;
private List<GraphicsDevice> cachedGraphics;
private boolean isUpdateVmTemplateVersion = false;
private String mdevType; // the value of the deprecated custom property mdev_type, if specified
private BiConsumer<AuditLogable, AuditLogDirector> affinityGroupLoggingMethod = (a, b) -> {};
public UpdateVmCommand(T parameters, CommandContext commandContext) {
super(parameters, commandContext);
}
@Override
protected void init() {
super.init();
if (getCluster() != null) {
setStoragePoolId(getCluster().getStoragePoolId());
}
if (isVmExist() && isCompatibilityVersionSupportedByCluster(getEffectiveCompatibilityVersion())) {
var givenVm = getParameters().getVmStaticData();
var customProperties = getVmPropertiesUtils().convertProperties(givenVm.getCustomProperties());
mdevType = customProperties.remove(MDevTypesUtils.DEPRECATED_CUSTOM_PROPERTY_NAME);
if (mdevType != null) {
givenVm.setCustomProperties(getVmPropertiesUtils().convertProperties(customProperties));
}
Version compatibilityVersion = getEffectiveCompatibilityVersion();
getVmPropertiesUtils().separateCustomPropertiesToUserAndPredefined(
compatibilityVersion, getParameters().getVmStaticData());
getVmPropertiesUtils().separateCustomPropertiesToUserAndPredefined(
compatibilityVersion, getVm().getStaticData());
}
vmHandler.updateDefaultTimeZone(getParameters().getVmStaticData());
vmHandler.autoSelectUsbPolicy(getParameters().getVmStaticData());
vmHandler.autoSelectResumeBehavior(getParameters().getVmStaticData());
vmHandler.autoSelectDefaultDisplayType(getVmId(),
getParameters().getVmStaticData(),
getCluster(),
getParameters().getGraphicsDevices());
updateParametersVmFromInstanceType();
initNuma();
updateUSB();
getVmDeviceUtils().setCompensationContext(getCompensationContextIfEnabledByCaller());
if (getParameters().getVmStaticData().getBiosType() == null) {
getParameters().getVmStaticData().setBiosType(getVm().getBiosType());
}
}
private VmPropertiesUtils getVmPropertiesUtils() {
return VmPropertiesUtils.getInstance();
}
@Override
protected LockProperties applyLockProperties(LockProperties lockProperties) {
return lockProperties.withScope(Scope.Command);
}
@Override
public AuditLogType getAuditLogTypeValue() {
return isInternalExecution() ?
getSucceeded() ? AuditLogType.SYSTEM_UPDATE_VM : AuditLogType.SYSTEM_FAILED_UPDATE_VM
: getSucceeded() ? AuditLogType.USER_UPDATE_VM : AuditLogType.USER_FAILED_UPDATE_VM;
}
@Override
protected void executeVmCommand() {
oldVm = getVm(); // needs to be here for post-actions
if (isUpdateVmTemplateVersion) {
updateVmTemplateVersion();
return; // template version was changed, no more work is required
}
newVmStatic = getParameters().getVmStaticData();
if (isRunningConfigurationNeeded()) {
logNameChange();
vmHandler.createNextRunSnapshot(
getVm(), getParameters().getVmStaticData(), getParameters(), getCompensationContextIfEnabledByCaller());
vmHandler.setVmDestroyOnReboot(getVm());
}
vmHandler.warnMemorySizeLegal(getParameters().getVm().getStaticData(), getEffectiveCompatibilityVersion());
// This cannot be reverted using compensation, but it should not be needed
vmStaticDao.incrementDbGeneration(getVm().getId());
newVmStatic.setCreationDate(oldVm.getStaticData().getCreationDate());
newVmStatic.setQuotaId(getQuotaId());
// save user selected value for hotplug before overriding with db values (when updating running vm)
VM userVm = new VM();
userVm.setStaticData(new VmStatic(newVmStatic));
if (newVmStatic.getCreationDate().equals(DateTime.getMinValue())) {
newVmStatic.setCreationDate(new Date());
}
if (getVm().isRunningOrPaused() && !shouldUpdateForHostedEngineOrKubevirt()) {
if (!vmHandler.copyNonEditableFieldsToDestination(
oldVm.getStaticData(),
newVmStatic,
isHotSetEnabled(),
oldVm.getStatus(),
getParameters().isMemoryHotUnplugEnabled())) {
// fail update vm if some fields could not be copied
throw new EngineException(EngineError.FAILED_UPDATE_RUNNING_VM);
}
} else {
updateVmNumaNodes();
}
if ((getVm().isRunningOrPaused() || getVm().isPreviewSnapshot() || getVm().isSuspended()) && !shouldUpdateForHostedEngineOrKubevirt()) {
if (getVm().getCustomCompatibilityVersion() == null && getParameters().getClusterLevelChangeFromVersion() != null) {
// For backward compatibility after cluster version change
// When running/paused: Set temporary custom compatibility version till the NextRun is applied (VM cold reboot)
// When snapshot in preview: keep the custom compatibility version even after commit or roll back by undo
newVmStatic.setCustomCompatibilityVersion(getParameters().getClusterLevelChangeFromVersion());
}
}
updateVmNetworks();
updateAffinityGroupsAndLabels();
if (!updateVmLease()) {
return;
}
if (isHotSetEnabled()) {
hotSetCpus(userVm);
updateCurrentMemory(userVm);
}
final List<Guid> oldIconIds = iconUtils.updateVmIcon(
oldVm.getStaticData(), newVmStatic, getParameters().getVmLargeIcon());
if (isCompensationEnabledByCaller()) {
VmStatic oldStatic = oldVm.getStaticData();
getCompensationContext().snapshotEntityUpdated(oldStatic);
}
resourceManager.getVmManager(getVmId()).update(newVmStatic);
// Hosted Engine and kubevirt doesn't use next-run snapshots. Instead it requires the configuration
// for next run to be stored in vm_static table.
if (getVm().isNotRunning() || shouldUpdateForHostedEngineOrKubevirt()) {
updateVmPayload();
getVmDeviceUtils().updateVmDevices(getParameters(), oldVm);
updateWatchdog();
updateRngDevice();
updateGraphicsDevices();
updateVmHostDevices();
updateVmDevicesOnEmulatedMachineChange();
updateVmDevicesOnChipsetChange();
updateMdevDevices();
}
iconUtils.removeUnusedIcons(oldIconIds, getCompensationContextIfEnabledByCaller());
vmHandler.updateVmInitToDB(getParameters().getVmStaticData(), getCompensationContextIfEnabledByCaller());
checkTrustedService();
liveUpdateCpuProfile();
// Persist all data in compensation context.
// It can be done here at the end, because the whole command runs in a transaction.
compensationStateChanged();
setSucceeded(true);
}
private void updateVmDevicesOnEmulatedMachineChange() {
if (isEmulatedMachineChanged()) {
log.info("Emulated machine has changed for VM: {} ({}), the device addresses will be removed.",
getVm().getName(),
getVm().getId());
getVmDeviceUtils().removeVmDevicesAddress(getVmId());
getVmDeviceUtils().resetVmDevicesHash(getVmId());
}
}
private void updateVmDevicesOnChipsetChange() {
if (isChipsetChanged()) {
log.info("BIOS chipset type has changed for VM: {} ({}), the disks and devices will be converted to new chipset.",
getVm().getName(),
getVm().getId());
getVmHandler().convertVmToNewChipset(getVmId(), getParameters().getVmStaticData().getBiosType().getChipsetType(), getCompensationContextIfEnabledByCaller());
}
}
private boolean shouldUpdateForHostedEngineOrKubevirt() {
return getVm().isHostedEngine() || !getVm().isManaged();
}
private void logNameChange() {
String runtimeName = vmDynamicDao.get(getVmId()).getRuntimeName();
String newName = newVmStatic.getName();
if (!newName.equals(oldVm.getName()) && !newName.equals(runtimeName)) {
log.info("changing the name of a vm that started as {} to {}", runtimeName, newName);
}
}
private boolean updateVmLease() {
if (Objects.equals(oldVm.getLeaseStorageDomainId(), newVmStatic.getLeaseStorageDomainId())) {
return true;
}
// Currently, compensation is only used when this command is called from UpdateClusterCommand,
// and it does not update VM leases.
// TODO - Add compensation support if needed.
throwIfCompensationEnabled();
if (getVm().isNotRunning()) {
if (!addVmLease(newVmStatic.getLeaseStorageDomainId(), newVmStatic.getId(), false)) {
return false;
}
} else if (isHotSetEnabled()) {
if (oldVm.getLeaseStorageDomainId() == null) {
return addVmLease(newVmStatic.getLeaseStorageDomainId(), newVmStatic.getId(), true);
}
boolean hotUnplugSucceeded = false;
try {
hotUnplugSucceeded = runVdsCommand(VDSCommandType.HotUnplugLease,
new LeaseVDSParameters(getVm().getRunOnVds(),
oldVm.getId(),
oldVm.getLeaseStorageDomainId())).getSucceeded();
} catch (EngineException e) {
log.error("Failure in hot unplugging a lease to VM {}, message: {}",
oldVm.getId(), e.getMessage());
}
if (!hotUnplugSucceeded) {
auditLog(this, AuditLogType.HOT_UNPLUG_LEASE_FAILED);
}
}
// In case of remove lease only, VM lease info should set to null
if (oldVm.getLeaseStorageDomainId() != null && newVmStatic.getLeaseStorageDomainId() == null) {
vmDynamicDao.updateVmLeaseInfo(getVmId(), null);
}
// best effort to remove the lease from the previous storage domain
removeVmLease(oldVm.getLeaseStorageDomainId(), oldVm.getId());
return true;
}
private void liveUpdateCpuProfile(){
if (getVm().getStatus().isQualifiedForQosChange() &&
!Objects.equals(oldVm.getCpuProfileId(), newVmStatic.getCpuProfileId())) {
vmSlaPolicyUtils.refreshCpuQosOfRunningVm(getVm());
}
}
private void updateVmHostDevices() {
if (isDedicatedVmForVdsChanged()) {
// Currently, compensation is only used when this command is called from UpdateClusterCommand,
// and it does not change preferred hosts of the VM.
// TODO - Add compensation support if needed.
throwIfCompensationEnabled();
log.info("Pinned host changed for VM: {}. Dropping configured host devices.", getVm().getName());
vmDeviceDao.removeVmDevicesByVmIdAndType(getVmId(), VmDeviceGeneralType.HOSTDEV);
}
}
private void updateMdevDevices() {
if (mdevType != null) {
var convertedMdevDevices = MDevTypesUtils.convertDeprecatedCustomPropertyToVmDevices(mdevType, getVmId());
vmDeviceDao.removeVmDevicesByVmIdAndType(getVmId(), VmDeviceGeneralType.MDEV);
vmDeviceDao.saveAll(convertedMdevDevices);
}
}
/**
* Handles a template-version update use case.
* If vm is down -> updateVmVersionCommand will handle the rest and will preform the actual change.
* if it's running -> a NEXT_RUN snapshot will be created and the change will take affect only on power down.
* in both cases the command should end after this function as no more changes are possible.
*/
private void updateVmTemplateVersion() {
if (getVm().getStatus() == VMStatus.Down) {
ActionReturnValue result =
runInternalActionWithTasksContext(
ActionType.UpdateVmVersion,
new UpdateVmVersionParameters(getVmId(),
getParameters().getVm().getVmtGuid(),
getParameters().getVm().isUseLatestVersion()),
getLock()
);
if (result.getSucceeded()) {
getTaskIdList().addAll(result.getInternalVdsmTaskIdList());
}
setSucceeded(result.getSucceeded());
setActionReturnValue(ActionType.UpdateVmVersion);
} else {
vmHandler.createNextRunSnapshot(getVm(),
getParameters().getVmStaticData(),
getParameters(),
getCompensationContextIfEnabledByCaller());
setSucceeded(true);
}
}
private void updateRngDevice() {
if (!getParameters().isUpdateRngDevice()) {
return;
}
QueryReturnValue query =
runInternalQuery(QueryType.GetRngDevice, new IdQueryParameters(getParameters().getVmId()));
List<VmRngDevice> rngDevs = query.getReturnValue();
ActionReturnValue rngCommandResult = null;
if (rngDevs.isEmpty()) {
if (getParameters().getRngDevice() != null) {
RngDeviceParameters params = new RngDeviceParameters(getParameters().getRngDevice(), true);
params.setCompensationEnabled(isCompensationEnabledByCaller());
rngCommandResult = runInternalAction(ActionType.AddRngDevice, params, cloneContextWithNoCleanupCompensation());
}
} else {
if (getParameters().getRngDevice() == null) {
RngDeviceParameters params = new RngDeviceParameters(rngDevs.get(0), true);
params.setCompensationEnabled(isCompensationEnabledByCaller());
rngCommandResult = runInternalAction(ActionType.RemoveRngDevice, params, cloneContextWithNoCleanupCompensation());
} else {
RngDeviceParameters params = new RngDeviceParameters(getParameters().getRngDevice(), true);
params.setCompensationEnabled(isCompensationEnabledByCaller());
params.getRngDevice().setDeviceId(rngDevs.get(0).getDeviceId());
rngCommandResult = runInternalAction(ActionType.UpdateRngDevice, params, cloneContextWithNoCleanupCompensation());
}
}
if (rngCommandResult != null && !rngCommandResult.getSucceeded()) {
log.error("Updating RNG device of VM {} ({}) failed. Old RNG device = {}. New RNG device = {}.",
getVm().getName(),
getVm().getId(),
rngDevs.isEmpty() ? null : rngDevs.get(0),
getParameters().getRngDevice());
}
}
private void hotSetCpus(VM newVm) {
int currentSockets = getVm().getNumOfSockets();
int newNumOfSockets = newVm.getNumOfSockets();
// try hotplug only if topology (cpuPerSocket, threadsPerCpu) hasn't changed
if (getVm().getStatus() == VMStatus.Up
&& VmCommonUtils.isCpusToBeHotpluggedOrUnplugged(getVm(), newVm)) {
HotSetNumberOfCpusParameters params =
new HotSetNumberOfCpusParameters(
newVmStatic,
currentSockets < newNumOfSockets ? PlugAction.PLUG : PlugAction.UNPLUG);
ActionReturnValue setNumberOfCpusResult =
runInternalAction(
ActionType.HotSetNumberOfCpus,
params, cloneContextAndDetachFromParent());
// Hosted engine VM does not care if hotplug failed. The requested CPU count is serialized
// into the OVF store and automatically used during the next HE VM start
if (!getVm().isHostedEngine()) {
newVmStatic.setNumOfSockets(setNumberOfCpusResult.getSucceeded() ? newNumOfSockets : currentSockets);
}
logHotSetActionEvent(setNumberOfCpusResult, AuditLogType.FAILED_HOT_SET_NUMBER_OF_CPUS);
}
}
private void updateCurrentMemory(VM newVm) {
int currentMemory = getVm().getMemSizeMb();
int newAmountOfMemory = newVm.getMemSizeMb();
if (getVm().getStatus().isNotRunning() || !getVm().isManaged()) {
newVmStatic.setMemSizeMb(newAmountOfMemory);
return;
}
if (getVm().getStatus() != VMStatus.Up) {
newVmStatic.setMemSizeMb(currentMemory);
log.warn("Memory update {}MB -> {}MB of VM {} ({}) left out. Memory can't be updated in current VM state ({}).",
currentMemory,
newAmountOfMemory,
getVm().getName(),
getVm().getId(),
getVm().getStatus());
return;
}
if (VmCommonUtils.isMemoryToBeHotplugged(getVm(), newVm)) {
// Temporarily setting to the currentMemory. It will be increased in hotPlugMemory().
newVmStatic.setMemSizeMb(currentMemory);
hotPlugMemory(newAmountOfMemory - currentMemory);
// Hosted engine VM does not care if hotplug failed. The requested memory size is serialized
// into the OVF store and automatically used during the next HE VM start
if (getVm().isHostedEngine()) {
newVmStatic.setMemSizeMb(newAmountOfMemory);
}
return;
}
if (currentMemory > newAmountOfMemory) {
final Lock lock = getVmManager().getVmDevicesLock();
lock.lock();
try {
hotUnplugMemory(newVm);
} finally {
lock.unlock();
}
}
}
/**
* Hot unplug of largest memory device that is smaller or equal to requested memory decrement.
*
* <p>The decrement is computed as 'memory of VM in DB' - 'memory of VM from Params'. However user sees value from
* next run snapshot in the Edit VM dialog.</p>
*
* <p>No matter if the memory hot unplug succeeds or not, the next run snapshot always contains values requested by
* user, not values rounded to the size of memory device.</p>
*/
private void hotUnplugMemory(VM newVm) {
if (!getParameters().isMemoryHotUnplugEnabled()) {
return;
}
final List<VmDevice> vmMemoryDevices = vmDeviceDao.getVmDeviceByVmIdTypeAndDevice(
getVmId(),
VmDeviceGeneralType.MEMORY,
VmDeviceType.MEMORY);
final int oldMemoryMb = oldVm.getMemSizeMb();
final int oldMinMemoryMb = oldVm.getMinAllocatedMem();
final List<VmDevice> memoryDevicesToUnplug = MemoryUtils.computeMemoryDevicesToHotUnplug(vmMemoryDevices,
oldMemoryMb, getParameters().getVm().getMemSizeMb(), getVmManager());
if (memoryDevicesToUnplug.isEmpty()) {
logNoDeviceToHotUnplug(vmMemoryDevices);
// Hosted Engine doesn't use next-run snapshots. Instead it requires the configuration for next run to be stored
// in vm_static table.
if (!oldVm.isHostedEngine()) {
newVmStatic.setMemSizeMb(oldMemoryMb);
newVmStatic.setMinAllocatedMem(oldMinMemoryMb);
}
return;
}
final int totalHotUnpluggedMemoryMb = memoryDevicesToUnplug.stream()
.mapToInt(deviceToHotUnplug -> {
final ActionReturnValue hotUnplugReturnValue = runInternalAction(
ActionType.HotUnplugMemoryWithoutVmUpdate,
new HotUnplugMemoryWithoutVmUpdateParameters(
deviceToHotUnplug.getId(),
newVm.getMinAllocatedMem()),
cloneContextAndDetachFromParent());
return hotUnplugReturnValue.getSucceeded()
? VmDeviceCommonUtils.getSizeOfMemoryDeviceMb(deviceToHotUnplug).get()
: 0;
})
.sum();
// Hosted Engine doesn't use next-run snapshots. Instead it requires the configuration for next run to be stored
// in vm_static table.
if (!oldVm.isHostedEngine()) {
newVmStatic.setMemSizeMb(oldMemoryMb - totalHotUnpluggedMemoryMb);
newVmStatic.setMinAllocatedMem(totalHotUnpluggedMemoryMb > 0 // at least one hot unplug succeeded
? newVm.getMinAllocatedMem()
: oldMinMemoryMb);
}
}
private void logNoDeviceToHotUnplug(List<VmDevice> vmMemoryDevices) {
final AuditLogType message = vmMemoryDevices.isEmpty()
? AuditLogType.NO_MEMORY_DEVICE_TO_HOT_UNPLUG
: AuditLogType.NO_SUITABLE_MEMORY_DEVICE_TO_HOT_UNPLUG;
if (!vmMemoryDevices.isEmpty()) {
final int originalMemoryMb = oldVm.getMemSizeMb();
addCustomValue(AUDIT_LOG_OLD_MEMORY_MB, String.valueOf(originalMemoryMb));
addCustomValue(AUDIT_LOG_NEW_MEMORY_MB, String.valueOf(getParameters().getVm().getMemSizeMb()));
final String unplugOptions = vmMemoryDevices.stream()
.filter(VmDeviceCommonUtils::isMemoryDeviceHotUnpluggable)
.filter(device -> !getVmManager().isDeviceBeingHotUnlugged(device.getDeviceId()))
.map(device -> VmDeviceCommonUtils.getSizeOfMemoryDeviceMb(device).get())
.map(deviceSize -> String.format(
"%dMB (%dMB)",
deviceSize,
memoryAfterHotUnplug(originalMemoryMb, deviceSize)))
.collect(Collectors.joining(", "));
addCustomValue(AUDIT_LOG_MEMORY_HOT_UNPLUG_OPTIONS, unplugOptions);
}
auditLogDirector.log(this, message);
}
private int memoryAfterHotUnplug(int originalMemory, int memoryDeviceSize) {
final int decrementedSize = originalMemory - memoryDeviceSize;
return decrementedSize > 0 ? decrementedSize : originalMemory;
}
/**
* Hot plug a memory device.
*
* <p>If there isn't memory device of minimal size (i.e. size of memory block), then hot plugged memory is split
* to two devices of sizes: minimal + the rest.</p>
*
* <p>Such behavior is shortcut of "the first hot plugged device has to be of minimal size". The reason for this is
* that the hot plugged memory is intended to be onlined (make available to the guest OS) as 'online_movable' for it
* to be unpluggable later on. Memory blocks can be onlined as movable only in order from higher addresses to lower
* addresses. Trying to online them in a different order fails. Kernel produces events to online memory blocks
* in arbitrary order, so not all of the blocks may be successfully onlined. But when a memory device of minimal
* size is hot plugged, it contains just a single memory block, so there is no ambiguity with memory block ordering
* and the block is always successfully onlined.</p>
*
* <p>Following memory hot plugs are not affected by this constraint because the first hot plug extends movable zone
* of the memory from the first hot plugged device to the end of the memory address space and memory blocks in this
* movable zone can be onlined as online movable in arbitrary order. Movable zone is not shrunk when memory devices
* are offlined and hot unplugged.</p>
*/
private void hotPlugMemory(int memoryHotPlugSize) {
final int minimalHotPlugDeviceSizeMb = getVm().getClusterArch().getHotplugMemorySizeFactorMb();
if (requiresSpecialBlock(minimalHotPlugDeviceSizeMb)) {
if (!hotPlugMemoryDevice(minimalHotPlugDeviceSizeMb)) {
// If the first hotplug fails, no need to execute the second call
return;
}
int secondPartSizeMb = memoryHotPlugSize - minimalHotPlugDeviceSizeMb;
if (secondPartSizeMb > 0) {
hotPlugMemoryDevice(secondPartSizeMb);
}
} else {
hotPlugMemoryDevice(memoryHotPlugSize);
}
}
private boolean requiresSpecialBlock(int minimalHotPlugDeviceSizeMb) {
if (!osRepository.requiresHotPlugSpecialBlock(getParameters().getVmStaticData().getOsId(),
getEffectiveCompatibilityVersion())){
return false;
}
final List<VmDevice> memoryDevices = getVmDeviceUtils().getMemoryDevices(getVmId());
final boolean minimalMemoryDevicePresent = memoryDevices.stream()
.anyMatch(device -> VmDeviceCommonUtils.getSizeOfMemoryDeviceMb(device)
.map(size -> size == minimalHotPlugDeviceSizeMb).orElse(false));
return !minimalMemoryDevicePresent;
}
private boolean hotPlugMemoryDevice(int memHotplugSize) {
HotSetAmountOfMemoryParameters params =
new HotSetAmountOfMemoryParameters(
newVmStatic,
memHotplugSize > 0 ? PlugAction.PLUG : PlugAction.UNPLUG,
// We always use node 0, auto-numa should handle the allocation
0,
memHotplugSize);
ActionReturnValue setAmountOfMemoryResult =
runInternalAction(
ActionType.HotSetAmountOfMemory,
params, cloneContextAndDetachFromParent());
if (setAmountOfMemoryResult.getSucceeded()) {
newVmStatic.setMemSizeMb(newVmStatic.getMemSizeMb() + memHotplugSize);
}
logHotSetActionEvent(setAmountOfMemoryResult, AuditLogType.FAILED_HOT_SET_MEMORY);
return setAmountOfMemoryResult.getSucceeded();
}
/**
* add audit log msg for failed hot set in case error was in CDA
* otherwise internal command will audit log the result
*/
private void logHotSetActionEvent(ActionReturnValue setActionResult, AuditLogType logType) {
if (!setActionResult.isValid()) {
AuditLogable logable = new AuditLogableImpl();
logable.setVmId(getVmId());
logable.setVmName(getVmName());
List<String> validationMessages = backend.getErrorsTranslator()
.translateErrorText(setActionResult.getValidationMessages());
logable.addCustomValue(HotSetNumberOfCpusCommand.LOGABLE_FIELD_ERROR_MESSAGE,
StringUtils.join(validationMessages, ","));
auditLogDirector.log(logable, logType);
}
}
private void checkTrustedService() {
if (getParameters().getVm().isTrustedService() && !getCluster().supportsTrustedService()) {
auditLogDirector.log(this, AuditLogType.USER_UPDATE_VM_FROM_TRUSTED_TO_UNTRUSTED);
} else if (!getParameters().getVm().isTrustedService() && getCluster().supportsTrustedService()) {
auditLogDirector.log(this, AuditLogType.USER_UPDATE_VM_FROM_UNTRUSTED_TO_TRUSTED);
}
}
private void updateWatchdog() {
// do not update if this flag is not set
if (getParameters().isUpdateWatchdog()) {
// Currently, compensation is only used when this command is called from UpdateClusterCommand,
// and it does not update watchdog.
// TODO - Add compensation support if needed.
throwIfCompensationEnabled();
QueryReturnValue query =
runInternalQuery(QueryType.GetWatchdog, new IdQueryParameters(getParameters().getVmId()));
List<VmWatchdog> watchdogs = query.getReturnValue();
if (watchdogs.isEmpty() && getParameters().getWatchdog() == null) {
return;
}
WatchdogParameters parameters = new WatchdogParameters();
parameters.setId(getParameters().getVmId());
if (getParameters().getWatchdog() != null) {
parameters.setAction(getParameters().getWatchdog().getAction());
parameters.setModel(getParameters().getWatchdog().getModel());
if(watchdogs.isEmpty()) {
runInternalAction(ActionType.AddWatchdog, parameters, cloneContextAndDetachFromParent());
} else {
// there is a watchdog in the vm, we have to update.
runInternalAction(ActionType.UpdateWatchdog, parameters, cloneContextAndDetachFromParent());
}
} else {
// there is a watchdog in the vm, there should not be any, so let's delete
runInternalAction(ActionType.RemoveWatchdog, parameters, cloneContextAndDetachFromParent());
}
}
}
private void updateGraphicsDevices() {
Set<Map.Entry<GraphicsType, GraphicsDevice>> entries =
getParameters().getGraphicsDevices().entrySet();
/* Devices have to be removed first and then added to prevent having multiple devices at the same time
which is sometimes prohibited (FeatureSupported.multipleGraphicsSupported) */
entries.stream().filter(entry -> entry.getValue() == null).forEach(entry -> removeGraphicsDevice(entry.getKey()));
entries.stream().filter(entry -> entry.getValue() != null).forEach(entry -> addOrUpdateGraphicsDevice(entry.getValue()));
}
private void removeGraphicsDevice(GraphicsType type) {
GraphicsDevice existingGraphicsDevice = getGraphicsDevOfType(type);
if (existingGraphicsDevice != null) {
GraphicsParameters params = new GraphicsParameters(existingGraphicsDevice);
params.setCompensationEnabled(isCompensationEnabledByCaller());
backend.runInternalAction(ActionType.RemoveGraphicsDevice, params, cloneContextWithNoCleanupCompensation());
}
}
private void addOrUpdateGraphicsDevice(GraphicsDevice device) {
GraphicsDevice existingGraphicsDevice = getGraphicsDevOfType(device.getGraphicsType());
if (existingGraphicsDevice != null) {
device.setDeviceId(existingGraphicsDevice.getDeviceId());
}
device.setVmId(getVmId());
GraphicsParameters params = new GraphicsParameters(device);
params.setCompensationEnabled(isCompensationEnabledByCaller());
backend.runInternalAction(
existingGraphicsDevice == null ? ActionType.AddGraphicsDevice : ActionType.UpdateGraphicsDevice,
params,
cloneContextWithNoCleanupCompensation());
}
private GraphicsDevice getGraphicsDevOfType(GraphicsType type) {
return getGraphicsDevices().stream().filter(dev -> dev.getGraphicsType() == type).findFirst().orElse(null);
}
private List<GraphicsDevice> getGraphicsDevices() {
if (cachedGraphics == null) {
cachedGraphics = backend
.runInternalQuery(QueryType.GetGraphicsDevices, new IdQueryParameters(getParameters().getVmId())).getReturnValue();
}
return cachedGraphics;
}
private void updateVmPayload() {
VmPayload payload = getParameters().getVmPayload();
if (payload != null || getParameters().isClearPayload()) {
// Currently, compensation is only used when this command is called from UpdateClusterCommand,
// and it does not update VM payload.
// TODO - Add compensation support if needed.
throwIfCompensationEnabled();
List<VmDevice> disks = vmDeviceDao.getVmDeviceByVmIdAndType(getVmId(), VmDeviceGeneralType.DISK);
VmDevice oldPayload = null;
for (VmDevice disk : disks) {
if (VmPayload.isPayload(disk.getSpecParams())) {
oldPayload = disk;
break;
}
}
if (oldPayload != null) {
vmDeviceDao.remove(oldPayload.getId());
}
if (!getParameters().isClearPayload()) {
getVmDeviceUtils().addManagedDevice(new VmDeviceId(Guid.newGuid(), getVmId()),
VmDeviceGeneralType.DISK,
payload.getDeviceType(),
payload.getSpecParams(),
true,
true);
}
}
}
private void updateVmNetworks() {
// check if the cluster has changed
if (!Objects.equals(getVm().getClusterId(), getParameters().getVmStaticData().getClusterId())) {
// Currently, compensation is only used when this command is called from UpdateClusterCommand,
// and it does not change cluster ID.
// TODO - Add compensation support if needed.
throwIfCompensationEnabled();
List<Network> networks =
networkDao.getAllForCluster(getParameters().getVmStaticData().getClusterId());
List<VmNic> interfaces = vmNicDao.getAllForVm(getParameters().getVmStaticData().getId());
for (final VmNic iface : interfaces) {
final Network network = networkHelper.getNetworkByVnicProfileId(iface.getVnicProfileId());
boolean networkFound = networks.stream().anyMatch(n -> Objects.equals(n.getId(), network.getId()));
// if network not exists in cluster we remove the network from the interface
if (!networkFound) {
iface.setVnicProfileId(null);
vmNicDao.update(iface);
}
}
}
}
private void updateVmNumaNodes() {
if (!getParameters().isUpdateNuma()) {
return;
}
// Currently, compensation is only used when this command is called from UpdateClusterCommand,
// and it does not change NUMA nodes.
// TODO - Add compensation support if needed.
throwIfCompensationEnabled();
List<VmNumaNode> newList = getParameters().getVmStaticData().getvNumaNodeList();
VmNumaNodeOperationParameters params =
new VmNumaNodeOperationParameters(getParameters().getVm(), new ArrayList<>(newList));
addLogMessages(backend.runInternalAction(ActionType.SetVmNumaNodes, params));
}
private void initNuma() {
List<VmNumaNode> vNumaNodeList = vmNumaNodeDao.getAllVmNumaNodeByVmId(getParameters().getVmId());
if (getVm() != null) {
getVm().setvNumaNodeList(vNumaNodeList);
}
if (VmCpuCountHelper.isResizeAndPinPolicy(getParameters().getVm())) {
getParameters().setUpdateNuma(false);
}
if (getParameters().isUpdateNuma() == null) {
getParameters().setUpdateNuma(!vNumaNodeList.equals(getParameters().getVm().getvNumaNodeList()));
}
// we always need to verify new or existing numa nodes with the updated VM configuration
if (!getParameters().isUpdateNuma()) {
getParameters().getVm().setvNumaNodeList(vNumaNodeList);
}
}
private void updateUSB() {
if (getParameters().getVmStaticData().getDefaultDisplayType() == DisplayType.none && !isConsoleEnabled()) {
getParameters().getVmStaticData().setUsbPolicy(UsbPolicy.DISABLED);
}
}
private boolean isConsoleEnabled() {
if (getParameters().isConsoleEnabled() != null) {
return getParameters().isConsoleEnabled();
} else {
return getVmDeviceUtils().hasConsoleDevice(getVmId());
}
}
private void addLogMessages(ActionReturnValue returnValueBase) {
if (!returnValueBase.getSucceeded()) {
auditLogDirector.log(this, AuditLogType.NUMA_UPDATE_VM_NUMA_NODE_FAILED);
}
}
@Override
protected List<Class<?>> getValidationGroups() {
addValidationGroup(UpdateVm.class);
return super.getValidationGroups();
}
@Override