@@ -14,6 +14,8 @@ import agent.AgentRegistryFactory
14
14
import cloud.template.AttachedVolume
15
15
import cloud.template.ProvisioningTemplateExtension
16
16
import com.fasterxml.jackson.module.kotlin.convertValue
17
+ import db.PluginRegistry
18
+ import db.PluginRegistryFactory
17
19
import db.SetupRegistryFactory
18
20
import db.VMRegistry
19
21
import db.VMRegistryFactory
@@ -35,6 +37,7 @@ import kotlinx.coroutines.delay
35
37
import kotlinx.coroutines.launch
36
38
import model.cloud.PoolAgentParams
37
39
import model.cloud.VM
40
+ import model.plugins.call
38
41
import model.retry.RetryPolicy
39
42
import model.setup.Setup
40
43
import model.setup.Volume
@@ -97,6 +100,22 @@ class CloudManager : CoroutineVerticle() {
97
100
private const val CLUSTER_MAP_CIRCUIT_BREAKERS = " CloudManager.Map.CircuitBreakers"
98
101
}
99
102
103
+ /* *
104
+ * Contains information about a VM to create
105
+ */
106
+ private data class VMToCreate (
107
+ /* *
108
+ * The VM to create (with the actual setup from which it should be created)
109
+ */
110
+ val vm : VM ,
111
+
112
+ /* *
113
+ * The original setup. May differ from the VM's setup if it has been
114
+ * modified by setup adapter plugins.
115
+ */
116
+ val originalSetup : Setup
117
+ )
118
+
100
119
/* *
101
120
* The client to connect to the Cloud
102
121
*/
@@ -156,6 +175,11 @@ class CloudManager : CoroutineVerticle() {
156
175
*/
157
176
private lateinit var setupCircuitBreakers: VMCircuitBreakerMap
158
177
178
+ /* *
179
+ * The plugin registry
180
+ */
181
+ private val pluginRegistry: PluginRegistry = PluginRegistryFactory .create()
182
+
159
183
/* *
160
184
* A cluster-wide semaphore to prevent [sync] from being called multiple
161
185
* times in parallel
@@ -475,7 +499,7 @@ class CloudManager : CoroutineVerticle() {
475
499
if (! cleanupOnly) {
476
500
// ensure there's a minimum number of VMs
477
501
launch {
478
- createRemoteAgent { setupSelector.selectMinimum(setups) }
502
+ createRemoteAgent(emptyList()) { setupSelector.selectMinimum(setups) }
479
503
}
480
504
}
481
505
} finally {
@@ -561,21 +585,44 @@ class CloudManager : CoroutineVerticle() {
561
585
break
562
586
}
563
587
564
- val result = createRemoteAgent { setupSelector.select(remaining, requiredCapabilities, possibleSetups) }
588
+ val result = createRemoteAgent(requiredCapabilities) {
589
+ setupSelector.select(remaining, requiredCapabilities, possibleSetups)
590
+ }
565
591
remaining = result.count { ! it.second }.toLong()
566
592
}
567
593
}
568
594
569
- private suspend fun createRemoteAgent (selector : suspend () -> List <Setup >): List <Pair <VM , Boolean >> {
595
+ /* *
596
+ * Applies all setup adapter plugins to the given setup and returns the new
597
+ * instance. If there are no plugins or if they did not make any modifications,
598
+ * the method returns the original setup.
599
+ */
600
+ private suspend fun applyPlugins (setup : Setup ,
601
+ requiredCapabilities : Collection <String >): Setup {
602
+ val adapters = pluginRegistry.getSetupAdapters()
603
+ var result = setup
604
+ for (adapter in adapters) {
605
+ result = adapter.call(result, requiredCapabilities, vertx)
606
+ }
607
+ return result
608
+ }
609
+
610
+ private suspend fun createRemoteAgent (requiredCapabilities : Collection <String >,
611
+ selector : suspend () -> List <Setup >): List <Pair <VM , Boolean >> {
570
612
// atomically create VM entries in the registry
571
613
val sharedData = vertx.sharedData()
572
614
val lock = sharedData.getLock(LOCK_VMS ).coAwait()
573
615
val vmsToCreate = try {
574
616
val setupsToCreate = selector()
575
617
setupsToCreate.map { setup ->
576
- VM (setup = setup).also {
577
- vmRegistry.addVM(it)
578
- } to setup
618
+ // call setup adapters and modify setup if necessary
619
+ val modifiedSetup = applyPlugins(setup, requiredCapabilities)
620
+
621
+ // add VM to registry
622
+ val vm = VM (setup = modifiedSetup)
623
+ vmRegistry.addVM(vm)
624
+
625
+ VMToCreate (vm = vm, originalSetup = setup)
579
626
}
580
627
} finally {
581
628
lock.release()
@@ -589,30 +636,30 @@ class CloudManager : CoroutineVerticle() {
589
636
* Return a list that contains pairs of a VM and a boolean telling if the
590
637
* VM was created successfully or not.
591
638
*/
592
- private suspend fun createRemoteAgents (vmsToCreate : List <Pair < VM , Setup > >): List <Pair <VM , Boolean >> {
639
+ private suspend fun createRemoteAgents (vmsToCreate : List <VMToCreate >): List <Pair <VM , Boolean >> {
593
640
val sharedData = vertx.sharedData()
594
- val deferreds = vmsToCreate.map { (vm, setup ) ->
641
+ val deferreds = vmsToCreate.map { (vm, originalSetup ) ->
595
642
// create multiple VMs in parallel
596
643
async {
597
644
// hold a lock as long as we are creating this VM
598
645
val creatingLock = sharedData.getLock(VM_CREATION_LOCK_PREFIX + vm.id).coAwait()
599
646
try {
600
- log.info(" Creating virtual machine ${vm.id} with setup `${setup.id} ' ..." )
647
+ log.info(" Creating virtual machine ${vm.id} with setup `${vm. setup.id} ' ..." )
601
648
602
- val delay = setupCircuitBreakers.computeIfAbsent(setup ).currentDelay
649
+ val delay = setupCircuitBreakers.computeIfAbsent(originalSetup ).currentDelay
603
650
if (delay > 0 ) {
604
651
log.info(" Backing off for $delay milliseconds due to too many failed attempts." )
605
652
delay(delay)
606
653
}
607
654
608
655
try {
609
656
// create VM
610
- val externalId = createVM(vm.id, setup)
657
+ val externalId = createVM(vm.id, vm. setup)
611
658
vmRegistry.setVMExternalID(vm.id, externalId)
612
659
vmRegistry.setVMCreationTime(vm.id, Instant .now())
613
660
614
661
// create other volumes in background
615
- val volumeDeferreds = createVolumesAsync(externalId, setup)
662
+ val volumeDeferreds = createVolumesAsync(externalId, vm. setup)
616
663
617
664
try {
618
665
cloudClient.waitForVM(externalId, timeoutCreateVM)
@@ -628,7 +675,7 @@ class CloudManager : CoroutineVerticle() {
628
675
vmRegistry.setVMStatus(vm.id, VM .Status .CREATING , VM .Status .PROVISIONING )
629
676
630
677
val attachedVolumes = volumes.map { AttachedVolume (it.first, it.second) }
631
- provisionVM(ipAddress, vm.id, externalId, setup, attachedVolumes)
678
+ provisionVM(ipAddress, vm.id, externalId, vm. setup, attachedVolumes)
632
679
} catch (e: Throwable ) {
633
680
vmRegistry.forceSetVMStatus(vm.id, VM .Status .DESTROYING )
634
681
cloudClient.destroyVM(externalId, timeoutDestroyVM)
@@ -646,12 +693,16 @@ class CloudManager : CoroutineVerticle() {
646
693
647
694
vmRegistry.setVMStatus(vm.id, VM .Status .PROVISIONING , VM .Status .RUNNING )
648
695
vmRegistry.setVMAgentJoinTime(vm.id, Instant .now())
649
- setupCircuitBreakers.afterAttemptPerformed(setup.id, true )
696
+ // always call setupCircuitBreakers with original setup ID! (see
697
+ // computeIfAbsent() call above)
698
+ setupCircuitBreakers.afterAttemptPerformed(originalSetup.id, true )
650
699
} catch (t: Throwable ) {
651
700
vmRegistry.forceSetVMStatus(vm.id, VM .Status .ERROR )
652
701
vmRegistry.setVMReason(vm.id, t.message ? : " Unknown error" )
653
702
vmRegistry.setVMDestructionTime(vm.id, Instant .now())
654
- setupCircuitBreakers.afterAttemptPerformed(setup.id, false )
703
+ // always call setupCircuitBreakers with original setup ID! (see
704
+ // computeIfAbsent() call above)
705
+ setupCircuitBreakers.afterAttemptPerformed(originalSetup.id, false )
655
706
throw t
656
707
}
657
708
} finally {
@@ -661,7 +712,7 @@ class CloudManager : CoroutineVerticle() {
661
712
}
662
713
663
714
return deferreds.mapIndexed { i, d ->
664
- vmsToCreate[i].first to try {
715
+ vmsToCreate[i].vm to try {
665
716
d.await()
666
717
true
667
718
} catch (t: Throwable ) {
@@ -699,7 +750,7 @@ class CloudManager : CoroutineVerticle() {
699
750
* objects that can be used to wait for the completion of the asynchronous
700
751
* operation and to obtain the IDs of the created volumes.
701
752
*/
702
- private suspend fun createVolumesAsync (externalId : String ,
753
+ private fun createVolumesAsync (externalId : String ,
703
754
setup : Setup ): List <Deferred <Pair <String , Volume >>> {
704
755
val metadata = mapOf (CREATED_BY to createdByTag, SETUP_ID to setup.id,
705
756
VM_EXTERNAL_ID to externalId)
0 commit comments