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:
earthmant 2015-10-15 15:30:23 +03:00 committed by Stan Lagun
parent 5bdb77491f
commit 44778cfc14
8 changed files with 351 additions and 0 deletions

View 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.

View File

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

View File

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

View File

@ -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='"')

View File

@ -0,0 +1 @@
cloudify-rest-client>=3.1

View 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

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