Adding the Cloudify Plugin Files
This plugin allows murano to deploy TOSCA applications using Cloudify. Partially-Implements: Blueprint support-tosca-cloudify-format Change-Id: I9eee5546016cea6503383b39536aee956597ed36
This commit is contained in:
parent
5bdb77491f
commit
44778cfc14
26
contrib/plugins/cloudify_plugin/README.rst
Normal file
26
contrib/plugins/cloudify_plugin/README.rst
Normal file
@ -0,0 +1,26 @@
|
||||
This is a Murano Plugin for Cloudify.
|
||||
|
||||
You need to install this plugin in your Murano environment using pip (pip install -e .).
|
||||
|
||||
You also need to upload the Cloudify Application (cloudify_application folder) to your Murano Packages:
|
||||
|
||||
cd cloudify_application
|
||||
zip -r cloudify_application.zip .
|
||||
|
||||
Then you will be able to deploy TOSCA applications using Cloudify.
|
||||
|
||||
To test this plugin you will need the cloudify-nodecellar-application.
|
||||
|
||||
Read "nodecellar_example_application/README.rst".
|
||||
|
||||
Download the Nodecellar Example to the nodecellar_example_application/Resources folder.
|
||||
|
||||
You will also need a manager. Follow these instructions to create a manager (http://getcloudify.org/guide/3.2/quickstart.html).
|
||||
|
||||
Then take Cloudify Manager IP and update your murano.conf file in ./murano/etc:
|
||||
|
||||
[cloudify]
|
||||
cloudify_manager = 10.10.1.10 # Change this.
|
||||
|
||||
Restart the Murano Engine and API.
|
||||
|
@ -0,0 +1,21 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
|
||||
def init_config(conf):
|
||||
opts = [
|
||||
cfg.StrOpt('cloudify_manager', required=True)
|
||||
]
|
||||
conf.register_opts(opts, group='cloudify')
|
||||
return conf.cloudify
|
@ -0,0 +1,88 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
import cloudify_rest_client
|
||||
import cloudify_rest_client.exceptions as cloudify_exceptions
|
||||
from murano.dsl import dsl
|
||||
from oslo_config import cfg as config
|
||||
from yaql.language import specs
|
||||
from yaql.language import yaqltypes
|
||||
|
||||
import cfg
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
archive_upload_lock = threading.Lock()
|
||||
|
||||
|
||||
class CloudifyClient(object):
|
||||
@specs.parameter('app', dsl.MuranoType('io.murano.Application'))
|
||||
def __init__(self, app):
|
||||
cloudify_manager = self.CONF.cloudify_manager
|
||||
self._client = cloudify_rest_client.CloudifyClient(cloudify_manager)
|
||||
self._blueprint_id = '{0}-{1}'.format(app.type.name, app.type.version)
|
||||
self._deployment_id = app.id
|
||||
self._application_package = app.package
|
||||
|
||||
@specs.parameter('entry_point', yaqltypes.String())
|
||||
def publish_blueprint(self, entry_point):
|
||||
global archive_upload_lock
|
||||
|
||||
if self._check_blueprint_exists():
|
||||
return
|
||||
path = self._application_package.get_resource(entry_point)
|
||||
with archive_upload_lock:
|
||||
try:
|
||||
self._client.blueprints.upload(
|
||||
path, self._blueprint_id)
|
||||
except cloudify_exceptions.CloudifyClientError as e:
|
||||
if e.status_code != 409:
|
||||
raise
|
||||
|
||||
def _check_blueprint_exists(self):
|
||||
try:
|
||||
self._client.blueprints.get(self._blueprint_id)
|
||||
return True
|
||||
except cloudify_exceptions.CloudifyClientError as e:
|
||||
if e.status_code == 404:
|
||||
return False
|
||||
raise
|
||||
|
||||
@specs.parameter('parameters', dict)
|
||||
def create_deployment(self, parameters=None):
|
||||
self._client.deployments.create(
|
||||
self._blueprint_id, self._deployment_id, parameters)
|
||||
|
||||
def delete_deployment(self):
|
||||
self._client.deployments.delete(self._deployment_id)
|
||||
|
||||
def wait_deployment_ready(self):
|
||||
while True:
|
||||
executions = self._client.executions.list(self._deployment_id)
|
||||
if any(t.status in ('pending', 'started') for t in executions):
|
||||
time.sleep(3)
|
||||
else:
|
||||
deployment = self._client.deployments.get(self._deployment_id)
|
||||
return deployment.outputs
|
||||
|
||||
@specs.parameter('name', yaqltypes.String())
|
||||
@specs.parameter('parameters', dict)
|
||||
def execute_workflow(self, name, parameters=None):
|
||||
self._client.executions.start(self._deployment_id, name, parameters)
|
||||
|
||||
@classmethod
|
||||
def init_plugin(cls):
|
||||
cls.CONF = cfg.init_config(CONF)
|
@ -0,0 +1,179 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import yaml
|
||||
|
||||
from murano.packages import exceptions
|
||||
from murano.packages import package_base
|
||||
|
||||
RESOURCES_DIR_NAME = 'Resources/'
|
||||
|
||||
|
||||
class YAQL(object):
|
||||
def __init__(self, expr):
|
||||
self.expr = expr
|
||||
|
||||
|
||||
class Dumper(yaml.SafeDumper):
|
||||
pass
|
||||
|
||||
|
||||
def yaql_representer(dumper, data):
|
||||
return dumper.represent_scalar(u'!yaql', data.expr)
|
||||
|
||||
|
||||
Dumper.add_representer(YAQL, yaql_representer)
|
||||
|
||||
|
||||
class CloudifyToscaPackage(package_base.PackageBase):
|
||||
def __init__(self, format_name, runtime_version, source_directory,
|
||||
manifest):
|
||||
super(CloudifyToscaPackage, self).__init__(
|
||||
format_name, runtime_version, source_directory, manifest)
|
||||
|
||||
self._entry_point = manifest.get('EntryPoint', 'main.yaml')
|
||||
self._generated_class = None
|
||||
self._generated_ui = None
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return self.full_name,
|
||||
|
||||
@property
|
||||
def requirements(self):
|
||||
return {
|
||||
'org.getcloudify.murano': '0'
|
||||
}
|
||||
|
||||
@property
|
||||
def ui(self):
|
||||
if not self._generated_ui:
|
||||
self._generated_ui = self._generate_ui()
|
||||
return self._generated_ui
|
||||
|
||||
def get_class(self, name):
|
||||
if name != self.full_name:
|
||||
raise exceptions.PackageClassLoadError(
|
||||
name, 'Class not defined in this package')
|
||||
if not self._generated_class:
|
||||
self._generated_class = self._generate_class()
|
||||
return self._generated_class, '<generated code>'
|
||||
|
||||
def _generate_class(self):
|
||||
inputs, outputs = self._get_inputs_outputs()
|
||||
class_code = {
|
||||
'Name': self.full_name,
|
||||
'Extends': 'org.getcloudify.murano.CloudifyApplication',
|
||||
'Properties': self._generate_properties(inputs, outputs),
|
||||
'Methods': {
|
||||
'describe': self._generate_describe_method(inputs),
|
||||
'updateOutputs': self._generate_update_outputs_method(outputs)
|
||||
}
|
||||
}
|
||||
return yaml.dump(class_code, Dumper=Dumper, default_style='"')
|
||||
|
||||
@staticmethod
|
||||
def _generate_properties(inputs, outputs):
|
||||
contracts = {}
|
||||
for name, value in inputs.iteritems():
|
||||
prop = {
|
||||
'Contract': YAQL('$.string().notNull()'),
|
||||
'Usage': 'In'
|
||||
}
|
||||
if 'default' in value:
|
||||
prop['Default'] = value['default']
|
||||
contracts[name] = prop
|
||||
|
||||
for name in outputs.iterkeys():
|
||||
contracts[name] = {
|
||||
'Contract': YAQL('$.string()'),
|
||||
'Usage': 'Out'
|
||||
}
|
||||
|
||||
return contracts
|
||||
|
||||
def _generate_describe_method(self, inputs):
|
||||
input_values = {
|
||||
name: YAQL('$.' + name)
|
||||
for name in inputs.iterkeys()
|
||||
}
|
||||
|
||||
return {
|
||||
'Body': [{
|
||||
'Return': {
|
||||
'entryPoint': self._entry_point,
|
||||
'inputs': input_values
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _generate_update_outputs_method(outputs):
|
||||
assignments = [
|
||||
{YAQL('$.' + name): YAQL('$outputs.get({0})'.format(name))}
|
||||
for name in outputs.iterkeys()
|
||||
]
|
||||
return {
|
||||
'Arguments': [{
|
||||
'outputs': {
|
||||
'Contract': {
|
||||
YAQL('$.string().notNull()'): YAQL('$')
|
||||
}
|
||||
}
|
||||
}],
|
||||
'Body': assignments
|
||||
}
|
||||
|
||||
def _get_inputs_outputs(self):
|
||||
path = os.path.join(
|
||||
self.source_directory, RESOURCES_DIR_NAME, self._entry_point)
|
||||
with open(path) as blueprint:
|
||||
data = yaml.safe_load(blueprint)
|
||||
return data.get('inputs') or {}, data.get('outputs') or {}
|
||||
|
||||
def _generate_application_ui_section(self, inputs):
|
||||
section = {
|
||||
key: YAQL('$.appConfiguration.' + key) for key in inputs.iterkeys()
|
||||
}
|
||||
section.update({
|
||||
'?': {
|
||||
'type': self.full_name
|
||||
}
|
||||
})
|
||||
return section
|
||||
|
||||
@staticmethod
|
||||
def _generate_form_ui_section(inputs):
|
||||
fields = [
|
||||
{
|
||||
'name': key,
|
||||
'label': key.title().replace('_', ' '),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'description': value.get('description', key)
|
||||
} for key, value in inputs.iteritems()
|
||||
]
|
||||
return [{
|
||||
'appConfiguration': {
|
||||
'fields': fields
|
||||
}
|
||||
}]
|
||||
|
||||
def _generate_ui(self):
|
||||
inputs, outputs = self._get_inputs_outputs()
|
||||
ui = {
|
||||
'Version': '2.2',
|
||||
'Application': self._generate_application_ui_section(inputs),
|
||||
'Forms': self._generate_form_ui_section(inputs)
|
||||
}
|
||||
return yaml.dump(ui, Dumper=Dumper, default_style='"')
|
1
contrib/plugins/cloudify_plugin/requirements.txt
Normal file
1
contrib/plugins/cloudify_plugin/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
cloudify-rest-client>=3.1
|
16
contrib/plugins/cloudify_plugin/setup.cfg
Normal file
16
contrib/plugins/cloudify_plugin/setup.cfg
Normal file
@ -0,0 +1,16 @@
|
||||
[metadata]
|
||||
name = io.murano.plugins.cloudify
|
||||
description = Murano-Cloudify integration plugin
|
||||
summary = Plugin to deploy Tosca packages via Cloudify Manager with Murano
|
||||
author = Trammell
|
||||
author-email = trammell@gigaspaces.com
|
||||
|
||||
[files]
|
||||
packages = murano_cloudify_plugin
|
||||
|
||||
[entry_points]
|
||||
io.murano.plugins.packages =
|
||||
Cloudify.TOSCA/1.0 = murano_cloudify_plugin.cloudify_tosca_package:CloudifyToscaPackage
|
||||
|
||||
io.murano.extensions =
|
||||
cloudify.CloudifyClient = murano_cloudify_plugin.cloudify_client:CloudifyClient
|
20
contrib/plugins/cloudify_plugin/setup.py
Normal file
20
contrib/plugins/cloudify_plugin/setup.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright 2011-2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import setuptools
|
||||
|
||||
# all other params will be taken from setup.cfg
|
||||
setuptools.setup(packages=setuptools.find_packages(),
|
||||
setup_requires=['pbr'], pbr=True)
|
Loading…
Reference in New Issue
Block a user