Commit d0a345a1 authored by Daniel Baur's avatar Daniel Baur

Initial draft of ghost blueprint

parent 727e8c51
########################################
# Ghost Blueprint
# - nginx as loadbalancer
# - ghost webapp on nodejs
# - postgresql as db backend
########################################
tosca_definitions_version: cloudify_dsl_1_0
imports:
- http://www.getcloudify.org/spec/cloudify/3.3m1/types.yaml
- http://www.getcloudify.org/spec/openstack-plugin/1.3m1/plugin.yaml
- http://www.getcloudify.org/spec/diamond-plugin/1.3m1/plugin.yaml
- types/ghost.yaml
- types/nginx.yaml
#####################################################################################
# inputs section allows the user to use same
# blueprint for creating different deployments, each one
# with its own parameters.
# to specify deployment inputs run:
# - cfy deployments create -b <blueprint_id> -d <deployment_id> -i inputs.yaml
#####################################################################################
inputs:
image:
description: >
Image to be used when launching agent VM's
flavor:
description: >
Flavor of the agent VM's
agent_user:
description: >
User for connecting to agent VM's
node_types:
###########################################################
# We define a type that inherits openstack's default
# server, and adds monitoring capabillities on top of it.
###########################################################
ghost.nodes.MonitoredServer:
derived_from: cloudify.openstack.nodes.Server
properties:
cloudify_agent:
default:
user: { get_input: agent_user }
server:
default:
image: { get_input: image }
flavor: { get_input: flavor }
interfaces:
###########################################################
# We are infact telling cloudify to install a diamond
# monitoring agent on the server.
#
# (see https://github.com/BrightcoveOS/Diamond)
###########################################################
cloudify.interfaces.monitoring_agent:
install:
implementation: diamond.diamond_agent.tasks.install
inputs:
diamond_config:
default:
interval: 1
start: diamond.diamond_agent.tasks.start
stop: diamond.diamond_agent.tasks.stop
uninstall: diamond.diamond_agent.tasks.uninstall
###########################################################
# Adding some collectors. These collectors are necessary
# for the Cloudify UI to display the deafult metrics.
###########################################################
cloudify.interfaces.monitoring:
start:
implementation: diamond.diamond_agent.tasks.add_collectors
inputs:
collectors_config:
default:
CPUCollector: {}
MemoryCollector: {}
LoadAverageCollector: {}
DiskUsageCollector:
config:
devices: x?vd[a-z]+[0-9]*$
NetworkCollector: {}
node_templates:
postgre_host:
type: ghost.nodes.MonitoredServer
relationships:
###########################################################
# Attaching the postgre security group to the postgre host
###########################################################
- target: mongod_security_group
type: cloudify.openstack.server_connected_to_security_group
nodejs_host:
type: ghost.nodes.MonitoredServer
###########################################################
# Setting the nodejs_host initial number of instances to 2.
# The default values for instances.deploy is 1.
###########################################################
instances:
deploy: 2
relationships:
###########################################################
# Attaching the ghost security group to
# the ghost host
###########################################################
- target: ghost_security_group
type: cloudify.openstack.server_connected_to_security_group
nginx_frontend_host:
type: ghost.nodes.MonitoredServer
relationships:
###########################################################
# Attaching a floating ip to the nginx frontend host
###########################################################
- type: cloudify.openstack.server_connected_to_floating_ip
target: nginx_floatingip
###########################################################
# Attaching the nginx frontend security group to
# the nginx frontend host
###########################################################
- type: cloudify.openstack.server_connected_to_security_group
target: nginx_frontend_security_group
postgre:
type: ghost.nodes.PostgreSQL
properties:
port: 27017
interfaces:
relationships:
- type: cloudify.relationships.contained_in
target: postgre_host
nodejs:
type: ghost.nodes.NodeJSServer
relationships:
- type: cloudify.relationships.contained_in
target: nodejs_host
ghost:
type: ghost.nodes.GhostApplicationModule
properties:
port: 8080
relationships:
################################
# Setting the postgre connection
################################
- type: node_connected_to_postgre
target: postgre
################################
# Setting the nodejs connection
################################
- type: node_contained_in_nodejs
target: nodejs
################################
# Setting the haproxy connection
################################
- type: app_connected_to_nginx
target: nginx
########################################
# Note: only ubuntu haproxy installation
# is supported.
########################################
nginx:
type: nginx.nodes.Proxy
relationships:
- target: nginx_frontend_host
type: cloudify.relationships.contained_in
###########################################################
# A security group to enable access to the nodejs host
# using the port of the ghost application.
#
# This security group will be attached to the nodejs_host
###########################################################
ghost_security_group:
type: cloudify.openstack.nodes.SecurityGroup
properties:
security_group:
name: ghost_security_group
rules:
- remote_ip_prefix: 0.0.0.0/0
port: 2368
###########################################################
# A security group to enable access to the postgre host
# using the port of the postgre node.
#
# We need this so that the ghost application can
# comminicate with PostgreSQL DB, since they are running on
# different hosts.
###########################################################
postgre_security_group:
type: cloudify.openstack.nodes.SecurityGroup
properties:
security_group:
name: postgre_security_group
rules:
- remote_ip_prefix: 0.0.0.0/0
port: 5432
###########################################################
# A security group to enable access to the nginx frontend
# host.
#
# This security group will be attached to the
# nginx_frontend_host
###########################################################
nginx_frontend_security_group:
type: cloudify.openstack.nodes.SecurityGroup
properties:
security_group:
name: nginx_frontend_security_group
rules:
- remote_ip_prefix: 0.0.0.0/0
port: 80
###########################################################
# A floating ip to be attached to the nginx frontend host,
# since eventually we want to be able to access the application
# from any machine, on any network.
###########################################################
frontend_floatingip:
type: cloudify.openstack.nodes.FloatingIP
###########################################################
# This outputs section exposes the application endpoint.
# You can access it by running:
# - cfy deployments -d <deployment_id> outputs
###########################################################
outputs:
endpoint:
description: Web application endpoint
value:
ip_address: { get_attribute: [ frontend_floatingip, floating_ip_address ] }
port: 80
\ No newline at end of file
image: 'c404edc0-0b8f-4749-bd94-dc65338c41f5'
flavor: '3'
agent_user: 'ubuntu'
\ No newline at end of file
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# http://wiki.nginx.org/Pitfalls
# http://wiki.nginx.org/QuickStart
# http://wiki.nginx.org/Configuration
#
# Generally, you will want to move this file somewhere, and start with a clean
# file but keep this around for reference. Or just disable in sites-enabled.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
upstream ghost {
{% for id, backend in backends.iteritems() %}
server {{ backend.address }}:2368
{% endfor %}
}
# Make site accessible from http://localhost/
server_name localhost;
location / {
proxy_pass http://ghost;
}
}
\ No newline at end of file
sudo apt-get install unzip
curl -L https://ghost.org/zip/ghost-latest.zip -o ghost.zip
unzip -uo ghost.zip -d ghost
cd ghost
\ No newline at end of file
#!/bin/bash -e
ctx logger info "Installing nginx"
ctx logger debug "${COMMAND}"
sudo apt-get update
sudo apt-get -y install nginx
ctx logger info "Installed nginx"
\ No newline at end of file
###############################################################################
# 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 subprocess
import tempfile
from contextlib import contextmanager
from jinja2 import Template
from cloudify_rest_client import exceptions as rest_exceptions
from cloudify import ctx
from cloudify.state import ctx_parameters as inputs
from cloudify import exceptions
from cloudify import utils
CONFIG_PATH = '/etc/nginx/sites-enabled/default'
TEMPLATE_RESOURCE_NAME = 'resources/nginx/nginx.default.template'
def configure(subject=None):
subject = subject or ctx
ctx.logger.info('Configuring nginx.')
template = Template(ctx.get_resource(TEMPLATE_RESOURCE_NAME))
ctx.logger.debug('Building a dict object that will contain variables '
'to write to the Jinja2 template.')
config = subject.node.properties.copy()
config.update(dict(
backends=subject.instance.runtime_properties.get('backends', {})))
ctx.logger.debug('Rendering the Jinja2 template to {0}.'.format(CONFIG_PATH))
ctx.logger.debug('The config dict: {0}.'.format(config))
with tempfile.NamedTemporaryFile(delete=False) as temp_config:
temp_config.write(template.render(config))
#_run('sudo /usr/sbin/haproxy -f {0} -c'.format(temp_config.name),
# error_message='Failed to Configure')
_run('sudo mv {0} {1}'.format(temp_config.name, CONFIG_PATH),
error_message='Failed to write to {0}.'.format(CONFIG_PATH))
def add_backend(backend_address=None):
with _backends_update() as backends:
backends[ctx.source.instance.id] = {
'address': backend_address or ctx.source.instance.host_ip
}
def remove_backend():
with _backends_update() as backends:
backends.pop(ctx.source.instance.id, None)
@contextmanager
def _backends_update():
backends = ctx.target.instance.runtime_properties.get('backends', {})
yield backends
ctx.target.instance.runtime_properties['backends'] = backends
# being explict because errors in unlink are ignored and
# not retried without being explicit.
# also, this way, we make sure that configure/reload
# are only called with a fully update configuration
try:
ctx.target.instance.update()
configure(subject=ctx.target)
_service('reload')
except rest_exceptions.CloudifyClientError as e:
if 'conflict' in str(e):
# cannot 'return' in contextmanager
ctx.operation.retry(
message='Backends updated concurrently, retrying.',
retry_after=1)
else:
raise
def start():
_service('start')
def stop():
_service('stop')
def _service(state):
_run('sudo service nginx {0}'.format(state),
error_message='Failed setting state to {0}'.format(state))
def _run(command, error_message):
runner = utils.LocalCommandRunner(logger=ctx.logger)
try:
runner.run(command)
except exceptions.CommandExecutionException as e:
raise NonRecoverableError('{0}: {1}'.format(error_message, e))
def _main():
invocation = inputs['invocation']
function = invocation['function']
args = invocation.get('args', [])
kwargs = invocation.get('kwargs', {})
globals()[function](*args, **kwargs)
if __name__ == '__main__':
_main()
\ No newline at end of file
#!/bin/bash -e
ctx logger info "Installing nodejs"
ctx logger debug "${COMMAND}"
sudo apt-get update
curl -sL https://deb.nodesource.com/setup_0.12 | sudo bash -
sudo apt-get install -y nodejs
sudo apt-get install -y build-essential
ctx logger info "Installed nodejs"
\ No newline at end of file
#!/bin/bash -e
ctx logger info "Installing postgre"
ctx logger debug "${COMMAND}"
sudo apt-get update
sudo apt-get -y install postgresql
ctx logger info "Installed postgre"
\ No newline at end of file
###############################################################################
# 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 subprocess
import tempfile
from contextlib import contextmanager
from jinja2 import Template
from cloudify_rest_client import exceptions as rest_exceptions
from cloudify import ctx
from cloudify.state import ctx_parameters as inputs
from cloudify import exceptions
from cloudify import utils
CONFIG_PATH = '/etc/nginx/sites-enabled/default'
TEMPLATE_RESOURCE_NAME = 'resources/nginx/nginx.default.template'
def configure(subject=None):
subject = subject or ctx
ctx.logger.info('Configuring nginx.')
template = Template(ctx.get_resource(TEMPLATE_RESOURCE_NAME))
ctx.logger.debug('Building a dict object that will contain variables '
'to write to the Jinja2 template.')
config = subject.node.properties.copy()
config.update(dict(
backends=subject.instance.runtime_properties.get('backends', {})))
ctx.logger.debug('Rendering the Jinja2 template to {0}.'.format(CONFIG_PATH))
ctx.logger.debug('The config dict: {0}.'.format(config))
with tempfile.NamedTemporaryFile(delete=False) as temp_config:
temp_config.write(template.render(config))
#_run('sudo /usr/sbin/haproxy -f {0} -c'.format(temp_config.name),
# error_message='Failed to Configure')
_run('sudo mv {0} {1}'.format(temp_config.name, CONFIG_PATH),
error_message='Failed to write to {0}.'.format(CONFIG_PATH))
def add_backend(backend_address=None):
with _backends_update() as backends:
backends[ctx.source.instance.id] = {
'address': backend_address or ctx.source.instance.host_ip
}
def remove_backend():
with _backends_update() as backends:
backends.pop(ctx.source.instance.id, None)
@contextmanager
def _backends_update():
backends = ctx.target.instance.runtime_properties.get('backends', {})
yield backends
ctx.target.instance.runtime_properties['backends'] = backends
# being explict because errors in unlink are ignored and
# not retried without being explicit.
# also, this way, we make sure that configure/reload
# are only called with a fully update configuration
try:
ctx.target.instance.update()
configure(subject=ctx.target)
_service('reload')
except rest_exceptions.CloudifyClientError as e:
if 'conflict' in str(e):
# cannot 'return' in contextmanager
ctx.operation.retry(
message='Backends updated concurrently, retrying.',
retry_after=1)
else:
raise
def start():
_service('start')
def stop():
_service('stop')
def _service(state):
_run('sudo service nginx {0}'.format(state),
error_message='Failed setting state to {0}'.format(state))
def _run(command, error_message):
runner = utils.LocalCommandRunner(logger=ctx.logger)
try:
runner.run(command)
except exceptions.CommandExecutionException as e:
raise NonRecoverableError('{0}: {1}'.format(error_message, e))
def _main():
invocation = inputs['invocation']
function = invocation['function']
args = invocation.get('args', [])
kwargs = invocation.get('kwargs', {})
globals()[function](*args, **kwargs)
if __name__ == '__main__':
_main()
\ No newline at end of file
#!/bin/bash
set -e
ctx source instance runtime_properties postgre_ip_address $(ctx target instance host_ip)
\ No newline at end of file
#!/bin/bash -e
ctx logger info "Starting postgre"
ctx logger debug "${COMMAND}"
sudo service postgresql start
ctx logger info "Started postgre"
\ No newline at end of file