SoftwareComponent implementation
Added two classed to implement two basic application workflows: installation and software configuration, with appropriate error handling, reporting, event-based notifications and so on. A class called 'SoftwareComponent' inherits both these classes and is able to run these two workflows sequentially to install and configure software on given Instance Groups. Change-Id: Ic0c1bbad1f0e3ae71b339c56db245075bcc420ac Targets-blueprint: application-development-framework
This commit is contained in:
parent
c4c8be61e0
commit
ef85e7ead9
|
@ -0,0 +1,367 @@
|
|||
Namespaces:
|
||||
=: io.murano.applications
|
||||
std: io.murano
|
||||
res: io.murano.resources
|
||||
m: io.murano.metadata.engine
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: BaseSoftwareComponent
|
||||
|
||||
Properties:
|
||||
allowedFailures:
|
||||
Contract: $.check($ in ['none', 'one', 'any', 'quorum'])
|
||||
Default: 'none'
|
||||
|
||||
componentName:
|
||||
Contract: $.string()
|
||||
Usage: Runtime
|
||||
|
||||
Methods:
|
||||
.init:
|
||||
Body:
|
||||
- $this._env: $.find(std:Environment).require()
|
||||
- $this.componentName: name($this)
|
||||
|
||||
deployAt:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $servers.deploy()
|
||||
|
||||
report:
|
||||
Arguments:
|
||||
- message:
|
||||
Contract: $.string().notNull()
|
||||
Body:
|
||||
- $this._env.reporter.report($this, $message)
|
||||
|
||||
_detect_success:
|
||||
Arguments:
|
||||
- numServers:
|
||||
Contract: $.int()
|
||||
- numFailures:
|
||||
Contract: $.int()
|
||||
Body:
|
||||
- Switch:
|
||||
$this.allowedFailures = none:
|
||||
- Return: $numFailures = 0
|
||||
$this.allowedFailures = one:
|
||||
- Return: $numFailures <= 1
|
||||
$this.allowedFailures = any:
|
||||
- Return: true
|
||||
$this.allowedFailures = quorum:
|
||||
- $maxFailures: $numServers - ($numServers/2 + 1)
|
||||
- Return: $numFailures <= $maxFailures
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: Installable
|
||||
Extends: BaseSoftwareComponent
|
||||
|
||||
Properties:
|
||||
beforeInstallEvent:
|
||||
Contract: $.class(Event).notNull()
|
||||
Usage: Runtime
|
||||
Default:
|
||||
name: beforeInstall
|
||||
|
||||
installServerEvent:
|
||||
Contract: $.class(Event).notNull()
|
||||
Usage: Runtime
|
||||
Default:
|
||||
name: installServer
|
||||
|
||||
completeInstallationEvent:
|
||||
Contract: $.class(Event).notNull()
|
||||
Usage: Runtime
|
||||
Default:
|
||||
name: completeInstallation
|
||||
|
||||
Methods:
|
||||
install:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $serversToInstall: $servers.items.where($this.checkServerNeedsInstallation($))
|
||||
- If: any($serversToInstall)
|
||||
Then:
|
||||
- $.beforeInstall($servers)
|
||||
- $results: $serversToInstall.pselect($this.installServer($))
|
||||
- $.completeInstallation($servers, $results.where(not $).len())
|
||||
|
||||
checkServerNeedsInstallation:
|
||||
Arguments:
|
||||
- server:
|
||||
Contract: $.class(res:Instance)
|
||||
Body:
|
||||
- Return: not $.getAttr(format('installed_at_{0}', id($server)), false)
|
||||
|
||||
beforeInstall:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $this.report(format('Installing {0}', $this.componentName))
|
||||
- $this.onBeforeInstall($servers)
|
||||
|
||||
onBeforeInstall:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $this.beforeInstallEvent.notify($this, $servers)
|
||||
|
||||
installServer:
|
||||
Meta:
|
||||
- m:Synchronize:
|
||||
onArgs: server
|
||||
Arguments:
|
||||
- server:
|
||||
Contract: $.class(res:Instance).notNull()
|
||||
Body:
|
||||
Try:
|
||||
- $this.report(format('Began installing {0} on {1}', $this.componentName,
|
||||
$server.name))
|
||||
- $this.onInstallServer($server)
|
||||
|
||||
Catch:
|
||||
- As: e
|
||||
Do:
|
||||
- $this.report(format('Unable to install {0} on {1} due to {2}',
|
||||
$this.componentName, $server.name, $e.message))
|
||||
- Return: False
|
||||
Else:
|
||||
- $this.report(format('{0} is installed on {1}',
|
||||
$this.componentName,
|
||||
$server.name))
|
||||
- $.setAttr(format('installed_at_{0}', id($server)), true)
|
||||
- Return: True
|
||||
|
||||
onInstallServer:
|
||||
Meta:
|
||||
- m:Synchronize:
|
||||
onArgs: server
|
||||
Arguments:
|
||||
- server:
|
||||
Contract: $.class(res:Instance).notNull()
|
||||
Body:
|
||||
- $this.installServerEvent.notify($this, $server)
|
||||
|
||||
|
||||
completeInstallation:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
- numFailures:
|
||||
Contract: $.int()
|
||||
Body:
|
||||
- $success: $this._detect_success($servers.items.len(), $numFailures)
|
||||
- If: $success
|
||||
Then:
|
||||
- $this.onCompleteInstallation($servers)
|
||||
|
||||
- $this.report(format('Finished installing {0} ({1} errors encountered)',
|
||||
$this.componentName, $numFailures or 'no'))
|
||||
Else:
|
||||
- Throw: TooManyInstallationErrors
|
||||
Message: format('Too many errors ({0}) encountered while installing {1}',
|
||||
$numFailures, $this.componentName)
|
||||
|
||||
onCompleteInstallation:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $this.completeInstallationEvent.notify($this, $servers)
|
||||
|
||||
deployAt:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- super($this, $.deployAt($servers))
|
||||
- $this.install($servers)
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: Configurable
|
||||
Extends: BaseSoftwareComponent
|
||||
|
||||
Properties:
|
||||
preConfigureEvent:
|
||||
Contract: $.class(Event).notNull()
|
||||
Usage: Runtime
|
||||
Default:
|
||||
name: preConfigure
|
||||
|
||||
configureServerEvent:
|
||||
Contract: $.class(Event).notNull()
|
||||
Usage: Runtime
|
||||
Default:
|
||||
name: configureServer
|
||||
|
||||
completeConfigurationEvent:
|
||||
Contract: $.class(Event).notNull()
|
||||
Usage: Runtime
|
||||
Default:
|
||||
name: completeConfiguration
|
||||
|
||||
Methods:
|
||||
.init:
|
||||
Body:
|
||||
- $this._randomName: randomName()
|
||||
|
||||
configure:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- If: not $this.checkClusterNeedsReconfiguration($servers)
|
||||
Then:
|
||||
- Return:
|
||||
- $this.preConfigure($servers)
|
||||
- $serversToConfigure: $servers.items.where($this.checkServerNeedsConfiguration($))
|
||||
- $results: $serversToConfigure.pselect($this.configureServer($))
|
||||
- $.completeConfiguration($servers, $results.where(not $).len())
|
||||
|
||||
checkClusterNeedsReconfiguration:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $key: $this.getKey()
|
||||
- $state: $this.getAttr(format('configuration_of_cluster_{0}', id($servers)), null)
|
||||
- Return: $key!=$state
|
||||
|
||||
checkServerNeedsConfiguration:
|
||||
Arguments:
|
||||
- server:
|
||||
Contract: $.class(res:Instance).notNull()
|
||||
Body:
|
||||
- $key: $this.getKey()
|
||||
- $state: $this.getAttr(format('configuration_of_server_{0}', id($server)), null)
|
||||
- Return: $key!=$state
|
||||
|
||||
getKey:
|
||||
Body:
|
||||
# should be redefined in subclasses to contain semantical signature
|
||||
# of the object's configuration
|
||||
- Return: $this._randomName
|
||||
|
||||
preConfigure:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $this.report(format('Applying configuration of {0}', $this.componentName))
|
||||
- $this.onPreConfigure($servers)
|
||||
|
||||
onPreConfigure:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $sr: $this.getSecurityRules()
|
||||
- If: $sr
|
||||
Then:
|
||||
- $this._env.securityGroupManager.addGroupIngress($sr)
|
||||
- $this._env.stack.push()
|
||||
- $this.preConfigureEvent.notify($this, $servers)
|
||||
|
||||
configureServer:
|
||||
Meta:
|
||||
- m:Synchronize:
|
||||
onArgs: server
|
||||
Arguments:
|
||||
- server:
|
||||
Contract: $.class(res:Instance).notNull()
|
||||
Body:
|
||||
- Try:
|
||||
- $this.report(format('Began configuring {0} on {1}',
|
||||
$this.componentName, $server.name))
|
||||
- $this.onConfigureServer($server)
|
||||
|
||||
Catch:
|
||||
- As: e
|
||||
Do:
|
||||
- $this.report(format('Unable to configure {0} on {1} due to {2}',
|
||||
$this.componentName, $server.name, $e.message))
|
||||
- Return: False
|
||||
Else:
|
||||
- $key: $this.getKey()
|
||||
- $this.setAttr(format('configuration_of_server_{0}', id($server)), $key)
|
||||
- $this.report(format('{0} is configured at {1}',
|
||||
$this.componentName, $server.name))
|
||||
- Return: True
|
||||
|
||||
onConfigureServer:
|
||||
Meta:
|
||||
- m:Synchronize:
|
||||
onArgs: server
|
||||
Arguments:
|
||||
- server:
|
||||
Contract: $.class(res:Instance).notNull()
|
||||
Body:
|
||||
- $this.configureServerEvent.notify($this, $server)
|
||||
|
||||
|
||||
completeConfiguration:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
- numFailures:
|
||||
Contract: $.int()
|
||||
Body:
|
||||
- $success: $this._detect_success($servers.items.len(), $numFailures)
|
||||
- If: $success
|
||||
Then:
|
||||
- $this.onCompleteConfiguration($servers)
|
||||
- $key: $this.getKey()
|
||||
- $this.setAttr(format('configuration_of_cluster_{0}', id($servers)),
|
||||
$key)
|
||||
- $this.report(format('Finished configuring {0} ({1} errors encountered)',
|
||||
$this.componentName, $numFailures or 'no'))
|
||||
Else:
|
||||
- Throw: TooManyConfigurationErrors
|
||||
Message: format('Too many errors ({0}) encountered while configuring {1}',
|
||||
$numFailures, $this.componentName)
|
||||
|
||||
onCompleteConfiguration:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- $this.completeConfigurationEvent.notify($this, $servers)
|
||||
|
||||
|
||||
getSecurityRules:
|
||||
Body:
|
||||
- Return: {}
|
||||
|
||||
deployAt:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- super($this, $.deployAt($servers))
|
||||
- $this.configure($servers)
|
||||
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: SoftwareComponent
|
||||
Extends:
|
||||
- Installable
|
||||
- Configurable
|
||||
|
||||
Methods:
|
||||
deployAt:
|
||||
Arguments:
|
||||
- servers:
|
||||
Contract: $.class(InstanceGroup).notNull()
|
||||
Body:
|
||||
- cast($this, Installable).deployAt($servers)
|
||||
- cast($this, Configurable).deployAt($servers)
|
|
@ -34,20 +34,38 @@ Properties:
|
|||
- $.class(res:Instance)
|
||||
|
||||
Methods:
|
||||
.init:
|
||||
Body:
|
||||
- $env: $.find(std:Environment)
|
||||
- If: $env
|
||||
Then:
|
||||
- $this._reporter: $env.reporter
|
||||
Else:
|
||||
- $this._reporter: null
|
||||
|
||||
deploy:
|
||||
Body:
|
||||
- cast($this, ReplicationGroup).deploy()
|
||||
- $this.items.select($.beginDeploy())
|
||||
- $this.items.select($this._deployInstance($))
|
||||
- $this.items.select($.endDeploy())
|
||||
|
||||
_deployInstance:
|
||||
Arguments:
|
||||
- instance:
|
||||
Contract: $.class(res:Instance)
|
||||
Body:
|
||||
- If: $this._reporter and name($instance) and not $instance.openstackId
|
||||
Then:
|
||||
- $this._reporter.report($this, 'Provisioning VM for ' + name($instance))
|
||||
- $instance.beginDeploy()
|
||||
|
||||
.destroy:
|
||||
Body:
|
||||
- $this.items.select($.beginReleaseResources())
|
||||
- $this.items.select($.endReleaseResources())
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
# A replication provider acting as a default factory class for
|
||||
# LinuxMuranoInstance (LMI)
|
||||
# A replication provider acting as a default factory class for Instances
|
||||
|
||||
Name: InstanceProvider
|
||||
Extends: CloneReplicaProvider
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
Namespaces:
|
||||
=: io.murano.applications.tests
|
||||
tst: io.murano.test
|
||||
apps: io.murano.applications
|
||||
res: io.murano.resources
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
Name: InstallableToTest
|
||||
Extends:
|
||||
- apps:Installable
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: ConfigurableToTest
|
||||
Extends:
|
||||
- apps:Configurable
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
|
||||
Name: SoftwareComponentToTest
|
||||
Extends:
|
||||
- apps:SoftwareComponent
|
||||
|
||||
|
||||
--- # ------------------------------------------------------------------ # ---
|
||||
Name: TestSoftwareComponent
|
||||
Extends: tst:TestFixtureWithEnvironment
|
||||
|
||||
Properties:
|
||||
reports:
|
||||
Contract:
|
||||
- $.string()
|
||||
Usage: Out
|
||||
Default: []
|
||||
|
||||
Methods:
|
||||
report:
|
||||
Arguments:
|
||||
- instance:
|
||||
Contract: $
|
||||
- message:
|
||||
Contract: $.string()
|
||||
Body:
|
||||
- $this.reports: $this.reports.append($message)
|
||||
|
||||
setUp:
|
||||
Body:
|
||||
- super($this, $.setUp())
|
||||
- inject(res:LinuxMuranoInstance, beginDeploy, '')
|
||||
- inject(res:LinuxMuranoInstance, endDeploy, '')
|
||||
- $instance: new(res:LinuxMuranoInstance, $this.environment,
|
||||
name=>'noop',
|
||||
image=>'noop',
|
||||
flavor=>'noop')
|
||||
- $provider: new(apps:InstanceProvider, $this.environment,
|
||||
source=>$instance, instanceNamePattern=>'testNode-{0}')
|
||||
- $this.group: new(apps:InstanceGroup, provider=>$provider, numItems=>5)
|
||||
- $this.group.deploy()
|
||||
- $this.reports: []
|
||||
- inject($this.environment.reporter, report, $this, report)
|
||||
|
||||
testInstallReportingSequence:
|
||||
Body:
|
||||
- $cmp: new(InstallableToTest, $this.environment, testComp)
|
||||
- $cmp.deployAt($this.group)
|
||||
- $this.assertInstallingSequence(0)
|
||||
|
||||
testConfigureReportingSequence:
|
||||
Body:
|
||||
- $cmp: new(ConfigurableToTest, $this.environment, testComp)
|
||||
- $cmp.deployAt($this.group)
|
||||
- $this.assertConfiguringSequence(0)
|
||||
|
||||
testCombinedSequence:
|
||||
Body:
|
||||
- $cmp: new(SoftwareComponentToTest, $this.environment, testComp)
|
||||
- $cmp.deployAt($this.group)
|
||||
- $this.assertInstallingSequence(0)
|
||||
- $this.assertConfiguringSequence(2*$this.group.numItems+2)
|
||||
|
||||
assertInstallingSequence:
|
||||
Arguments:
|
||||
- offset:
|
||||
Contract: $.int().notNull()
|
||||
Body:
|
||||
- $this.assertEqual('Installing testComp', $this.reports[$offset])
|
||||
- $nodeReports: range(0, $this.group.numItems*2).select($this.reports[$offset+1+$])
|
||||
- range(1, $this.group.numItems+1).select(
|
||||
$this.assertTrue(format('Began installing testComp on testNode-{0}', $) in $nodeReports))
|
||||
- range(1, $this.group.numItems+1).select(
|
||||
$this.assertTrue(format('testComp is installed on testNode-{0}', $) in $nodeReports))
|
||||
- $this.assertEqual('Finished installing testComp (no errors encountered)', $this.reports[$offset+2*$this.group.numItems+1])
|
||||
|
||||
assertConfiguringSequence:
|
||||
Arguments:
|
||||
- offset:
|
||||
Contract: $.int().notNull()
|
||||
Body:
|
||||
- $this.assertEqual('Applying configuration of testComp', $this.reports[$offset+0])
|
||||
- $nodeReports: range(0, $this.group.numItems*2).select($this.reports[$offset+1+$])
|
||||
- range(1, $this.group.numItems+1).select(
|
||||
$this.assertTrue(format('Began configuring testComp on testNode-{0}', $) in $nodeReports))
|
||||
- range(1, $this.group.numItems+1).select(
|
||||
$this.assertTrue(format('testComp is configured at testNode-{0}', $) in $nodeReports))
|
||||
- $this.assertEqual('Finished configuring testComp (no errors encountered)', $this.reports[$offset+2*$this.group.numItems+1])
|
||||
|
|
@ -34,6 +34,12 @@ Classes:
|
|||
io.murano.applications.InstanceGroup: instance.yaml
|
||||
io.murano.applications.InstanceProvider: instance.yaml
|
||||
|
||||
io.murano.applications.BaseSoftwareComponent: component.yaml
|
||||
io.murano.applications.Installable: component.yaml
|
||||
io.murano.applications.Configurable: component.yaml
|
||||
io.murano.applications.SoftwareComponent: component.yaml
|
||||
|
||||
|
||||
# Tests
|
||||
io.murano.applications.tests.TestReplication: tests/TestReplication.yaml
|
||||
io.murano.applications.tests.TestCloneReplicaProvider: tests/TestCloneReplicaProvider.yaml
|
||||
|
@ -42,3 +48,4 @@ Classes:
|
|||
io.murano.applications.tests.NestedReplicationTarget: tests/TestTemplateReplicaProvider.yaml
|
||||
io.murano.applications.tests.TestEvents: tests/TestEvents.yaml
|
||||
io.murano.applications.tests.TestMockedInstanceFactory: tests/TestInstanceProviders.yaml
|
||||
io.murano.applications.tests.TestSoftwareComponent: tests/TestSoftwareComponent.yaml
|
||||
|
|
Loading…
Reference in New Issue