Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ local

django-cloudlaunch/cloudlaunchserver/settings_local.py
django-cloudlaunch/db.sqlite3*
django-cloudlaunch/cloudlaunch.log
django-cloudlaunch/cloudlaunch*.log
django-cloudlaunch/cloudlaunchserver/db.sqlite3
django-cloudlaunch/baselaunch/backend_plugins/rancher_ansible*

Expand Down
10 changes: 10 additions & 0 deletions django-cloudlaunch/cloudlaunch/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,18 @@ def instance_type(self, obj):
return app_config.get('config_cloudlaunch', {}).get('instanceType')


class PublicKeyInline(admin.StackedInline):
model = models.PublicKey
extra = 1


class UserProfileAdmin(admin.ModelAdmin):
inlines = [PublicKeyInline]


admin.site.register(models.Application, AppAdmin)
admin.site.register(models.AppCategory, AppCategoryAdmin)
admin.site.register(models.ApplicationDeployment, AppDeploymentsAdmin)
admin.site.register(models.CloudImage, CloudImageAdmin)
admin.site.register(models.Usage, UsageAdmin)
admin.site.register(models.UserProfile, UserProfileAdmin)
104 changes: 81 additions & 23 deletions django-cloudlaunch/cloudlaunch/backend_plugins/app_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,43 +61,101 @@ def sanitise_app_config(app_config):
pass

@abc.abstractmethod
def launch_app(self, provider, task, name, cloud_config, app_config,
user_data):
def deploy(self, name, task, app_config, provider_config):
"""
Launch a given application on the target infrastructure.
Deploy this app plugin on the supplied provider.

Perform all the necessary steps to deploy this appliance. This may
involve provisioning cloud resources or configuring existing host(s).
See the definition of each method argument as some have required
structure.

This operation is designed to be a Celery task, and thus, can contain
long-running operations.

@type provider: :class:`CloudBridge.CloudProvider`
@param provider: Cloud provider where the supplied deployment is to be
created.
@type name: ``str``
@param name: Name of this deployment.

@type task: :class:`Task`
@param task: A Task object, which can be used to report progress. See
``tasks.Task`` for the interface details and sample
implementation.

@type name: ``str``
@param name: Name of this deployment.

@type cloud_config: ``dict``
@param cloud_config: A dict containing cloud infrastructure specific
configuration for this app.

@type app_config: ``dict``
@param app_config: A dict containing the original, unprocessed version
of the app config. The app config is a merged dict
of database stored settings and user-entered
settings.

@type user_data: ``object``
@param user_data: An object returned by the ``process_app_config()``
method which contains a validated and processed
version of the ``app_config``.
@param app_config: A dict containing the appliance configuration. The
app config is a merged dict of database stored
settings and user-entered settings. In addition to
the static configuration of the app, such as
firewall rules or access password, this should
contain a url to a host configuration playbook, if
such configuration step is desired. For example:
```
{
"config_cloudman": {},
"config_appliance": {
"sshUser": "ubuntu",
"runner": "ansible",
"repository": "https://github.com/afgane/Rancher-Ansible",
"inventoryTemplate": "https://gist.githubusercontent.com/..."
},
"config_cloudlaunch": {
"vmType": "c3.large",
"firewall": [ {
"securityGroup": "cloudlaunch-cm2",
"rules": [ {
"protocol": "tcp",
"from": "22",
"to": "22",
"cidr": "0.0.0.0/0"
} ] } ] } }
```
@type provider_config: ``dict``
@param provider_config: Define the details of of the infrastructure
provider where the appliance should be
deployed. It is expected that this dictionary
is composed within a task calling the plugin so
it reflects the supplied info and derived
properties. See ``tasks.py → create_appliance``
for an example.
The following keys are supported:
* ``cloud_provider``: CloudBridge object of the
cloud provider
* ``cloud_config``: A dict containing cloud
infrastructure specific
configuration for this app
* ``cloud_user_data``: An object returned by
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps validated_config instead of user_data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we have app_config and provider_config as the top level parameters in this method, I guess this should then be called validated_app_config but why is the top level app_config not validated then, which doesn't seem ideal. Further, all this field is ever used for is instance user data. We're also hoping to get away from using instance user data for anything but passing the config ssh key so I feel it's perhaps ok to just leave it as is.

``process_app_config()``
method which contains a
validated and formatted
version of the
``app_config`` to be
supplied as instance
user data
* ``host_address``: A host IP address or a
hostnames where to deploy
this appliance
* ``ssh_user``: User name with which to access
the host(s)
* ``ssh_public_key``: Public RSA ssh key to be
used when running the app
configuration step. This
should be the actual key.
CloudLaunch will auto-gen
this key for provisioned
instances. For hosted
instances, the user
should retrieve
CloudLaunch's public key
but this value should not
be supplied.
* ``ssh_private_key``: Private portion of an
RSA ssh key. This should
not be supplied by a
user and is intended
only for internal use.

:rtype: ``dict``
:return: Results of the launch process.
:return: Results of the deployment process.
"""
pass

Expand Down
80 changes: 62 additions & 18 deletions django-cloudlaunch/cloudlaunch/backend_plugins/base_vm_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import yaml
import ipaddress

from celery.utils.log import get_task_logger
from cloudbridge.cloud.base.helpers import generate_key_pair
from cloudbridge.cloud.interfaces import InstanceState
from cloudbridge.cloud.interfaces.resources import TrafficDirection
import requests
import requests.exceptions

from .app_plugin import AppPlugin

import logging
log = logging.getLogger(__name__)
log = get_task_logger('cloudlaunch')


class BaseVMAppPlugin(AppPlugin):
Expand Down Expand Up @@ -247,10 +246,39 @@ def resolve_launch_properties(self, provider, cloudlaunch_config):
provider, subnet, cloudlaunch_config['firewall'])
return subnet, placement, vmf

def launch_app(self, provider, task, name, cloud_config,
app_config, user_data):
"""Initiate the app launch process."""
def deploy(self, name, task, app_config, provider_config):
"""See the parent class in ``app_plugin.py`` for the docstring."""
p_result = {}
c_result = {}
if provider_config.get('host_address'):
# A host is provided; use CloudLaunch's default published ssh key
pass # Implement this once we actually support it
else:
if app_config.get('config_appliance'):
# Host config will take place; generate a tmp ssh config key
public_key, private_key = generate_key_pair()
provider_config['ssh_private_key'] = private_key
provider_config['ssh_public_key'] = public_key
provider_config['ssh_user'] = app_config.get(
'config_appliance', {}).get('sshUser')
p_result = self._provision_host(name, task, app_config,
provider_config)
provider_config['host_address'] = p_result['cloudLaunch'].get(
'publicIP')

if app_config.get('config_appliance'):
c_result = self._configure_host(name, task, app_config,
provider_config)
# Merge result dicts; right-most dict keys take precedence
return {**p_result, **c_result}

def _provision_host(self, name, task, app_config, provider_config):
"""Provision a host using the provider_config info."""
cloudlaunch_config = app_config.get("config_cloudlaunch", {})
provider = provider_config.get('cloud_provider')
cloud_config = provider_config.get('cloud_config')
user_data = provider_config.get('cloud_user_data') or ""

custom_image_id = cloudlaunch_config.get("customImageID", None)
img = provider.compute.images.get(
custom_image_id or cloud_config.get('image_id'))
Expand All @@ -268,27 +296,35 @@ def launch_app(self, provider, task, name, cloud_config,
vm_type = cloudlaunch_config.get(
'vmType', cloud_config.get('default_instance_type'))

log.debug("Launching with subnet %s and VM firewalls %s" %
(subnet, vmfl))
log.info("Launching base_vm of type %s with UD:\n%s" % (vm_type,
user_data))
task.update_state(state='PROGRESSING',
meta={'action': "Launching an instance of type %s "
"with keypair %s in zone %s" %
(vm_type, kp.name, placement_zone)})
log.debug("Launching with subnet %s and VM firewalls %s", subnet, vmfl)

if provider_config.get('ssh_public_key'):
# cloud-init config to allow login w/ the config ssh key
# http://cloudinit.readthedocs.io/en/latest/topics/examples.html
log.info("Adding a cloud-init config public ssh key to user data")
user_data += """
#cloud-config
ssh_authorized_keys:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it allowed to have multiple ssh_authorized_keys sections in user_data? Just wondering in case user_data already contains user supplied keys, but probably not something we need to necessarily address right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Given we've never used this in the past let's just see how things go.

- {0}""".format(provider_config['ssh_public_key'])
log.info("Launching base_vm of type %s with UD:\n%s", vm_type,
user_data)
task.update_state(state="PROGRESSING",
meta={"action": "Launching an instance of type %s "
"with keypair %s in zone %s" %
(vm_type, kp.name, placement_zone)})
inst = provider.compute.instances.create(
name=name, image=img, vm_type=vm_type, subnet=subnet,
key_pair=kp, vm_firewalls=vmfl, zone=placement_zone,
user_data=user_data, launch_config=cb_launch_config)
task.update_state(state='PROGRESSING',
meta={'action': "Waiting for instance %s" % inst.id})
task.update_state(state="PROGRESSING",
meta={"action": "Waiting for instance %s" % inst.id})
log.debug("Waiting for instance {0} to be ready...".format(inst.id))
inst.wait_till_ready()
static_ip = cloudlaunch_config.get('staticIP')
if static_ip:
task.update_state(state='PROGRESSING',
meta={'action': "Assigning requested floating "
"IP: %s" % static_ip})
"IP: %s" % static_ip})
inst.add_floating_ip(static_ip)
inst.refresh()
results = {}
Expand All @@ -306,6 +342,14 @@ def launch_app(self, provider, task, name, cloud_config,
"Public IP: %s" % results.get('publicIP') or ""})
return {"cloudLaunch": results}

def _configure_host(self, name, task, app_config, provider_config):
host = provider_config.get('host_address')
task.update_state(
state='PROGRESSING',
meta={"action": "Configuring host % s" % host})
log.info("Configuring host %s", host)
return {}

def _get_deployment_iid(self, deployment):
"""
Extract instance ID for the supplied deployment.
Expand Down
Loading