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:
Stan Lagun 2016-08-22 11:40:54 -07:00 committed by Konstantin Snihyr
parent 0a379e58e7
commit 6395787409
4 changed files with 233 additions and 23 deletions

View File

@ -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]

View File

@ -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: []

View File

@ -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)

View File

@ -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