From 63957874093ad7de039835e138cc2ba08c979299 Mon Sep 17 00:00:00 2001 From: Stan Lagun Date: Mon, 22 Aug 2016 11:40:54 -0700 Subject: [PATCH] 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 --- .../Classes/replication.yaml | 120 ++++++++++++++++-- .../Classes/servers.yaml | 39 ++++-- .../Classes/tests/TestReplication.yaml | 88 +++++++++++++ meta/io.murano.applications/manifest.yaml | 9 +- 4 files changed, 233 insertions(+), 23 deletions(-) diff --git a/meta/io.murano.applications/Classes/replication.yaml b/meta/io.murano.applications/Classes/replication.yaml index ca2cc7f2..3b5c358b 100644 --- a/meta/io.murano.applications/Classes/replication.yaml +++ b/meta/io.murano.applications/Classes/replication.yaml @@ -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] diff --git a/meta/io.murano.applications/Classes/servers.yaml b/meta/io.murano.applications/Classes/servers.yaml index 0092a6f2..0e53a1c0 100644 --- a/meta/io.murano.applications/Classes/servers.yaml +++ b/meta/io.murano.applications/Classes/servers.yaml @@ -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: [] + + diff --git a/meta/io.murano.applications/Classes/tests/TestReplication.yaml b/meta/io.murano.applications/Classes/tests/TestReplication.yaml index ee917c00..0c1aae13 100644 --- a/meta/io.murano.applications/Classes/tests/TestReplication.yaml +++ b/meta/io.murano.applications/Classes/tests/TestReplication.yaml @@ -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) diff --git a/meta/io.murano.applications/manifest.yaml b/meta/io.murano.applications/manifest.yaml index b0734dbd..a10a6e9f 100644 --- a/meta/io.murano.applications/manifest.yaml +++ b/meta/io.murano.applications/manifest.yaml @@ -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