Adds more replica provider primitives
This commit introduces several basic building blocks for replication providers: * PoolReplicaProvider returns replica from the prepopulated pool (that can become empty over time). Released replicas are returned to the pool. * RoundrobinReplicaProvider returns items from the prepopulated list. When the list is exhausted it returns them again thus reusing the items. This is usually needed to spread the load between fixed number of servers or other cloud resources. * CompositeReplicaProvider allows to combine several replica providers into one. When new replica is requested it tries to obtain it from the underlay providers one by so if the first replica provider goes out of resources the second is used for further allocations. Also refactor replica release interface to better handle down-scaling by more than 1 item Change-Id: I923b2c6d0cd3a881be323399b7b13481e9a4a459
This commit is contained in:
parent
0a379e58e7
commit
6395787409
@ -45,17 +45,16 @@ Methods:
|
||||
deploy:
|
||||
Body:
|
||||
# release excessive replicas
|
||||
- $newItems: $this.items.take($this.numItems)
|
||||
- $oldItems: $this.items.take($this.numItems)
|
||||
- $itemsToRelease: $this.items.skip($this.numItems)
|
||||
- $this.provider.releaseReplicas($itemsToRelease)
|
||||
- $this.items: $newItems
|
||||
|
||||
- $delta: $this.numItems - len($this.items)
|
||||
- $startIndex: len($this.items) + 1
|
||||
- $endIndex: $startIndex + $delta
|
||||
- $createdItems: range($startIndex, $endIndex).select(
|
||||
$this.provider.createReplica($, $this))
|
||||
- $this.items: $this.items + $createdItems
|
||||
$this.provider.createReplica($, $this)).takeWhile($ != null)
|
||||
- $this.items: $oldItems + $createdItems
|
||||
|
||||
scale:
|
||||
Arguments:
|
||||
@ -69,11 +68,10 @@ Methods:
|
||||
- $this.numItems: min($this.numItems, $this.maxItems)
|
||||
- $this.deploy()
|
||||
|
||||
releaseItems:
|
||||
Arguments:
|
||||
items:
|
||||
Contract:
|
||||
- $.class(std:Object)
|
||||
.destroy:
|
||||
Body:
|
||||
- $this.numItems: 0
|
||||
- $this.deploy()
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
@ -92,6 +90,8 @@ Methods:
|
||||
replicas:
|
||||
Contract:
|
||||
- $.class(std:Object)
|
||||
Body:
|
||||
Return: $replicas
|
||||
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
@ -114,3 +114,105 @@ Methods:
|
||||
|
||||
Body:
|
||||
- Return: new($this.template, $owner)
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
# Replica provider that is a composition of other replica providers
|
||||
|
||||
Name: CompositeReplicaProvider
|
||||
Extends: ReplicaProvider
|
||||
|
||||
Properties:
|
||||
providers:
|
||||
Contract:
|
||||
- $.class(ReplicaProvider).notNull()
|
||||
|
||||
Methods:
|
||||
createReplica:
|
||||
Arguments:
|
||||
- index:
|
||||
Contract: $.int().notNull()
|
||||
- owner:
|
||||
Contract: $.class(std:Object)
|
||||
Body:
|
||||
- Return: $this.providers.select($.createReplica($index, $owner)).where($ != null).first(null)
|
||||
|
||||
releaseReplicas:
|
||||
Arguments:
|
||||
replicas:
|
||||
Contract:
|
||||
- $.class(std:Object)
|
||||
Body:
|
||||
- For: provider
|
||||
In: $this.providers
|
||||
Do:
|
||||
- $replicas: $provider.releaseReplicas($replicas)
|
||||
- If: $replicas = null or not $replicas.any()
|
||||
Then:
|
||||
Break:
|
||||
- Return: $replicas
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
# A replica provider from the prepopulated pool
|
||||
|
||||
Name: PoolReplicaProvider
|
||||
Extends: ReplicaProvider
|
||||
|
||||
Properties:
|
||||
pool:
|
||||
Contract:
|
||||
- $.class(std:Object).notNull()
|
||||
|
||||
consumedItems:
|
||||
Usage: Out
|
||||
Contract:
|
||||
- $.class(std:Object).notNull()
|
||||
|
||||
Methods:
|
||||
createReplica:
|
||||
Arguments:
|
||||
- index:
|
||||
Contract: $.int().notNull()
|
||||
- owner:
|
||||
Contract: $.class(std:Object)
|
||||
Body:
|
||||
- $item: $this.pool.where(not $this.consumedItems.contains($)).first(null)
|
||||
- If: $item != null
|
||||
Then:
|
||||
- $this.consumedItems: $this.consumedItems.append($item)
|
||||
- Return: $item
|
||||
|
||||
releaseReplicas:
|
||||
Arguments:
|
||||
replicas:
|
||||
Contract:
|
||||
- $.class(std:Object)
|
||||
Body:
|
||||
- $poolReplicas: $replicas.where($this.consumedItems.contains($))
|
||||
- $this.consumedItems: $this.consumedItems.where(not $poolReplicas.contains($))
|
||||
- Return: $replicas.where(not $poolReplicas.contains($))
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
# Replica provider with a load balancing that returns instance from the
|
||||
# prepopulated list. Once the provider runs out of free items it goes to
|
||||
# beginning of the list and returns the same instances again.
|
||||
|
||||
Name: RoundrobinReplicaProvider
|
||||
Extends: ReplicaProvider
|
||||
|
||||
Properties:
|
||||
items:
|
||||
Contract:
|
||||
- $.class(std:Object).notNull()
|
||||
- 1
|
||||
|
||||
Methods:
|
||||
createReplica:
|
||||
Arguments:
|
||||
- index:
|
||||
Contract: $.int().notNull()
|
||||
- owner:
|
||||
Contract: $.class(std:Object)
|
||||
Body:
|
||||
- $index: $.getAttr(lastIndex, 0)
|
||||
- $.setAttr(lastIndex, ($index + 1) mod len($this.items))
|
||||
- Return: $this.items[$index]
|
||||
|
@ -79,7 +79,7 @@ Methods:
|
||||
|
||||
.destroy:
|
||||
Body:
|
||||
- $this.releaseServers($this.items)
|
||||
- $this.releaseServers($this.servers)
|
||||
|
||||
getServers:
|
||||
Body:
|
||||
@ -156,10 +156,6 @@ Methods:
|
||||
- cast($this, ReplicationGroup).deploy()
|
||||
- $this.deployServers($this, $this.items)
|
||||
|
||||
.destroy:
|
||||
Body:
|
||||
- $this.releaseServers($this.items)
|
||||
|
||||
getServers:
|
||||
Body:
|
||||
Return: $.items
|
||||
@ -197,6 +193,14 @@ Properties:
|
||||
serverNamePattern:
|
||||
Contract: $.string().notNull()
|
||||
|
||||
allocated:
|
||||
Usage: Out
|
||||
Contract: $.int().notNull()
|
||||
Default: 0
|
||||
|
||||
capacity:
|
||||
Contract: $.int()
|
||||
|
||||
Methods:
|
||||
createReplica:
|
||||
Arguments:
|
||||
@ -205,22 +209,31 @@ Methods:
|
||||
- owner:
|
||||
Contract: $.class(std:Object)
|
||||
Body:
|
||||
- $template: $this.template
|
||||
- $template.name: $this.serverNamePattern.format($index)
|
||||
- $ownerGroup: $this.find(ServerGroup)
|
||||
- If: $ownerGroup and name($ownerGroup)
|
||||
- If: $this.capacity = null or $this.allocated < $this.capacity
|
||||
Then:
|
||||
- $template: $this.template
|
||||
- $template.name: $this.serverNamePattern.format($index)
|
||||
- $ownerGroup: $this.find(ServerGroup)
|
||||
- If: $ownerGroup and name($ownerGroup)
|
||||
Then:
|
||||
- $groupName: format(' ({0})', name($ownerGroup))
|
||||
Else:
|
||||
Else:
|
||||
- $groupName: ''
|
||||
- $template['?'].name: format('Server {0}{1}', $index, $groupName)
|
||||
- Return: new($template, $owner)
|
||||
- $template['?'].name: format('Server {0}{1}', $index, $groupName)
|
||||
- $this.allocated: $this.allocated + 1
|
||||
- Return: new($template, $owner)
|
||||
Else:
|
||||
- Return: null
|
||||
|
||||
releaseReplicas:
|
||||
Arguments:
|
||||
replicas:
|
||||
Contract:
|
||||
- $.class(std:Object)
|
||||
- $.class(res:Instance)
|
||||
Body:
|
||||
- $replicas.select($.beginReleaseResources())
|
||||
- $replicas.select($.endReleaseResources())
|
||||
- $this.allocated: max(0, $this.allocated - len($replicas))
|
||||
- Return: []
|
||||
|
||||
|
||||
|
@ -93,3 +93,91 @@ Methods:
|
||||
- $.assertEqual(1, len($group.items))
|
||||
- $.assertEqual(1, $this.provider.allocated)
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: TestPoolReplicaProvider
|
||||
Extends: tst:TestFixture
|
||||
|
||||
Methods:
|
||||
setUp:
|
||||
Body:
|
||||
- $this.object1: new(std:Object)
|
||||
- $this.object2: new(std:Object)
|
||||
- $this.provider: new(apps:PoolReplicaProvider, pool => [$this.object1, $this.object2])
|
||||
|
||||
testReplicas:
|
||||
Body:
|
||||
- $.assertEqual(2, len($this.provider.pool))
|
||||
- $.assertEqual(0, len($this.provider.consumedItems))
|
||||
- $obj: $this.provider.createReplica(1, $this)
|
||||
- $.assertEqual($this.object1, $obj)
|
||||
- $.assertEqual(2, len($this.provider.pool))
|
||||
- $.assertEqual(1, len($this.provider.consumedItems))
|
||||
- $obj: $this.provider.createReplica(2, $this)
|
||||
- $.assertEqual($this.object2, $obj)
|
||||
- $.assertEqual(2, len($this.provider.pool))
|
||||
- $.assertEqual(2, len($this.provider.consumedItems))
|
||||
- $obj: $this.provider.createReplica(3, $this)
|
||||
- $.assertEqual(null, $obj)
|
||||
- $.assertEqual(2, len($this.provider.pool))
|
||||
- $.assertEqual(2, len($this.provider.consumedItems))
|
||||
|
||||
testReleaseReplicas:
|
||||
Body:
|
||||
- $obj: $this.provider.createReplica(1, $this)
|
||||
- $.assertEqual(1, len($this.provider.consumedItems))
|
||||
- $foreignObj: new(std:Object)
|
||||
- $res: $this.provider.releaseReplicas([$obj, $this.object1, $this.object2, $foreignObj])
|
||||
- $.assertEqual(0, len($this.provider.consumedItems))
|
||||
- $.assertEqual([$this.object2, $foreignObj], $res)
|
||||
- $this.testReplicas()
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: TestRoundrobinReplicaProvider
|
||||
Extends: tst:TestFixture
|
||||
|
||||
Methods:
|
||||
setUp:
|
||||
Body:
|
||||
- $this.object1: new(std:Object)
|
||||
- $this.object2: new(std:Object)
|
||||
- $this.provider: new(apps:RoundrobinReplicaProvider, items => [$this.object1, $this.object2])
|
||||
|
||||
testReplicas:
|
||||
Body:
|
||||
- $obj: $this.provider.createReplica(1, $this)
|
||||
- $.assertEqual($this.object1, $obj)
|
||||
- $obj: $this.provider.createReplica(2, $this)
|
||||
- $.assertEqual($this.object2, $obj)
|
||||
- $obj: $this.provider.createReplica(3, $this)
|
||||
- $.assertEqual($this.object1, $obj)
|
||||
- $obj: $this.provider.createReplica(4, $this)
|
||||
- $.assertEqual($this.object2, $obj)
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: TestCompositeReplicaProvider
|
||||
Extends: tst:TestFixture
|
||||
|
||||
Methods:
|
||||
setUp:
|
||||
Body:
|
||||
- $this.objects: range(4).select(new(std:Object))
|
||||
- $this.object2: new(std:Object)
|
||||
- $this.provider1: new(apps:PoolReplicaProvider, pool => [$this.objects[0], $this.objects[1]])
|
||||
- $this.provider2: new(apps:RoundrobinReplicaProvider, items => [$this.objects[2], $this.objects[3]])
|
||||
- $this.provider: new(apps:CompositeReplicaProvider, providers => [$this.provider1, $this.provider2])
|
||||
|
||||
testReplicas:
|
||||
Body:
|
||||
- $obj: $this.provider.createReplica(1, $this)
|
||||
- $.assertEqual($this.objects[0], $obj)
|
||||
- $obj: $this.provider.createReplica(2, $this)
|
||||
- $.assertEqual($this.objects[1], $obj)
|
||||
- $obj: $this.provider.createReplica(3, $this)
|
||||
- $.assertEqual($this.objects[2], $obj)
|
||||
- $obj: $this.provider.createReplica(4, $this)
|
||||
- $.assertEqual($this.objects[3], $obj)
|
||||
- $obj: $this.provider.createReplica(5, $this)
|
||||
- $.assertEqual($this.objects[2], $obj)
|
||||
|
@ -10,7 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
Format: 1.3
|
||||
Format: 1.4
|
||||
|
||||
Type: Library
|
||||
|
||||
@ -27,6 +27,10 @@ Classes:
|
||||
io.murano.applications.ReplicaProvider: replication.yaml
|
||||
io.murano.applications.ReplicationGroup: replication.yaml
|
||||
io.murano.applications.CloneReplicaProvider: replication.yaml
|
||||
io.murano.applications.CompositeReplicaProvider: replication.yaml
|
||||
io.murano.applications.PoolReplicaProvider: replication.yaml
|
||||
io.murano.applications.RoundrobinReplicaProvider: replication.yaml
|
||||
|
||||
io.murano.applications.Event: events.yaml
|
||||
|
||||
io.murano.applications.ServerGroup: servers.yaml
|
||||
@ -51,6 +55,9 @@ Classes:
|
||||
|
||||
# Tests
|
||||
io.murano.applications.tests.TestReplication: tests/TestReplication.yaml
|
||||
io.murano.applications.tests.TestPoolReplicaProvider: tests/TestReplication.yaml
|
||||
io.murano.applications.tests.TestRoundrobinReplicaProvider: tests/TestReplication.yaml
|
||||
io.murano.applications.tests.TestCompositeReplicaProvider: tests/TestReplication.yaml
|
||||
io.murano.applications.tests.TestEvents: tests/TestEvents.yaml
|
||||
io.murano.applications.tests.TestMockedServerFactory: tests/TestServerProviders.yaml
|
||||
io.murano.applications.tests.TestSoftwareComponent: tests/TestSoftwareComponent.yaml
|
||||
|
Loading…
x
Reference in New Issue
Block a user