Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit to django1.4 project

  • Loading branch information...
commit 6b38dac08b05c77d19cae7e3d28a2545c4ebe313 0 parents
@buzztroll buzztroll authored
10 manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "phantomweb.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
0  phantomweb/__init__.py
No changes.
11 phantomweb/admin.py
@@ -0,0 +1,11 @@
+from django.contrib import admin
+from phantomweb.models import PhantomInfoDB, DefaultCloudsDB
+
+class PhantomInfoAdmin(admin.ModelAdmin):
+ pass
+admin.site.register(PhantomInfoDB, PhantomInfoAdmin)
+
+class DefaultCloudsDBAdmin(admin.ModelAdmin):
+ pass
+admin.site.register(DefaultCloudsDB, DefaultCloudsDBAdmin)
+
BIN  phantomweb/djangotest.db
Binary file not shown
27 phantomweb/models.py
@@ -0,0 +1,27 @@
+from django.db import models
+
+class PhantomInfoDB(models.Model):
+ phantom_url = models.CharField(max_length=128)
+ dburl = models.CharField(max_length=128)
+
+class DefaultCloudsDB(models.Model):
+ name = models.CharField(max_length=128)
+ url = models.CharField(max_length=128)
+
+class UserPhantomInfoDB(models.Model):
+ username = models.CharField(max_length=128)
+ phantom_key = models.CharField(max_length=128)
+ phantom_secret = models.CharField(max_length=128)
+ phantom_url = models.CharField(max_length=128)
+ public_key = models.CharField(max_length=1024)
+
+class UserCloudInfoDB(models.Model):
+ cloudname = models.CharField(max_length=128)
+ username = models.CharField(max_length=128)
+ iaas_key = models.CharField(max_length=128)
+ iaas_secret = models.CharField(max_length=128)
+ cloud_url = models.CharField(max_length=128)
+
+
+
+
13 phantomweb/phantom_web_exceptions.py
@@ -0,0 +1,13 @@
+class PhantomWebException(Exception):
+
+ def __init__(self, message):
+ Exception.__init__(self, message)
+ self.message = message
+
+
+class PhantomRedirectException(Exception):
+
+ def __init__(self, location, message):
+ Exception.__init__(self, message)
+ self.message = message
+ self.redir = location
161 phantomweb/settings.py
@@ -0,0 +1,161 @@
+# Django settings for phantomweb project.
+import os
+import django
+
+DJANGO_ROOT = os.path.dirname(os.path.realpath(django.__file__))
+SITE_ROOT = os.path.dirname(os.path.realpath(__file__))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': os.path.join(SITE_ROOT, 'djangotest.db'), # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale.
+USE_L10N = True
+
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ os.path.join(SITE_ROOT, 'static'),
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'xfn8!ylndi2c&*divjvxor4az=-yhpobzo1w%addxx99b-ur3!'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ # Uncomment the next line for simple clickjacking protection:
+ # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'phantomweb.urls'
+
+# Python dotted path to the WSGI application used by Django's runserver.
+WSGI_APPLICATION = 'phantomweb.wsgi.application'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ os.path.join(SITE_ROOT, 'templates'),
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django.contrib.admin',
+ 'django.contrib.admindocs',
+ 'django.contrib.admindocs',
+ 'phantomweb',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error when DEBUG=False.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'filters': {
+ 'require_debug_false': {
+ '()': 'django.utils.log.RequireDebugFalse'
+ }
+ },
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'filters': ['require_debug_false'],
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+ 'loggers': {
+ 'django.request': {
+ 'handlers': ['mail_admins'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ }
+}
271 phantomweb/static/css/phantom.css
@@ -0,0 +1,271 @@
+.formbutton
+{
+ width: 80px;
+ text-align: Center;
+}
+
+#phantom_content
+{
+ display: table;
+ width: 900px;
+ height: 500px;
+}
+
+#main_combined_pane
+{
+ float: left;
+ position: relative;
+ width: 400px;
+}
+
+
+#main_lc_pane
+{
+ position: relative;
+ width: 400px;
+ float: left;
+}
+
+#main_asg_pane
+{
+ position: relative;
+ width: 400px;
+ float: right;
+}
+
+.phantom_domain_row
+{
+ width: 100%;
+ padding-top: 5px;
+ display: table;
+}
+
+.image_radio
+{
+ width: 10px;
+}
+
+.combo_label
+{
+ width: 50%;
+ display: inline;
+ float: left;
+}
+
+.combo_value
+{
+ width: 50%;
+ display: inline;
+ float: right;
+}
+
+.select_label_div
+{
+ position: relative;
+}
+
+.image_choice_div
+{
+ display: inline;
+}
+
+.imagechoice
+{
+ position: relative;
+ left: 0px;
+ width: 150px;
+}
+
+.image_choice_div
+{
+ position: relative;
+ left: 200px;
+}
+
+.form_pane
+{
+ border: solid;
+ border-color: black;
+ border-width: 1px;
+ padding: 10px;
+ margin: 4px;
+ height: 450px;
+}
+
+#asg_name_div
+{
+ display: table;
+ width: 100%;
+}
+
+#asg_name_lbl
+{
+ position: relative;
+ width: 50%;
+}
+
+#asg_name_input
+{
+ width: 50%;
+}
+
+#main_combined_pane
+{
+ position: relative;
+ width: 400px;
+ text-align: left;
+}
+
+.phantom_label
+{
+ width: 50%;
+ display: inline;
+ float: left;
+}
+
+#domain_name_div
+{
+}
+
+#domain_list_choices
+{
+ width: 400px;
+ height: 150px;
+}
+
+.phantom_section_div
+{
+ border-bottom: solid;
+ border-bottom-width: 1px;
+ padding-top: 0px;
+ padding-bottom: 4px;
+}
+
+#domain_name_lbl
+{
+ position: relative;
+ width: 50%;
+}
+
+#domain_list_div
+{
+ width: 100%;
+ height: 150px;
+}
+
+#domain_size_lbl
+{
+ position: relative;
+ width: 50%;
+}
+
+#domain_name_input
+{
+ width: 50%;
+}
+
+#domain_size_input
+{
+ width: 2pc;
+}
+
+#start_domain_button
+{
+ float: left;
+ margin-left: 50px;
+}
+
+#terminate_domain_button
+{
+ float: right;
+ margin-right: 50px;
+}
+
+#domain_button_div
+{
+ padding-top: 5px;
+ margin-top: 5px;
+}
+
+#domain_details_pane
+{
+ position: relative;
+ width: 400px;
+ float: right;
+}
+
+#domain_details_list_div
+{
+ overflow-y: scroll;
+ height: 90%;
+}
+
+
+.instance_status_div
+{
+}
+
+.instance_status_name
+{
+}
+
+.instance_status_details_list
+{
+}
+
+.instance_status_details_item
+{
+}
+
+#loading_image_div
+{
+ height: 20px;
+ position: relative;
+}
+
+#details_loading_image_div
+{
+ display: none;
+}
+
+#phantom_loaded_content
+{
+ display: none;
+}
+
+#main_loading_image
+{
+ display: none;
+}
+
+#status_bar_div
+{
+ height: 25px;
+}
+
+.phantom_links
+{
+ display: inline;
+}
+
+#public_key_input
+{
+ height: 100px;
+ width: 650px;
+}
+
+.phantom_setting_label
+{
+ width: 20%;
+}
+
+.phantom_setting_input
+{
+ width: 80%;
+}
+
+#phantom_settings_table
+{
+ width: 100%;
+}
+
BIN  phantomweb/static/images/loading1.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  phantomweb/static/images/loading4.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  phantomweb/static/images/loading_bar.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
321 phantomweb/static/js/phantom.js
@@ -0,0 +1,321 @@
+function disable_buttons(bool, msg) {
+ if(bool) {
+ $("#error_status_text").html(msg);
+ $("#start_domain_button").attr("disabled", "disabled");
+ $("#terminate_domain_button").attr("disabled", "disabled");
+ $("#domain_list_choices").attr("disabled", "disabled");
+ $("#location_choices").attr("disabled", "disabled");
+ $('#main_loading_image').show();
+ }
+ else {
+ $("#error_status_text").html("Ready.");
+ $("#start_domain_button").removeAttr("disabled", "disabled");
+ $("#terminate_domain_button").removeAttr("disabled", "disabled");
+ $("#domain_list_choices").removeAttr("disabled", "disabled");
+ $("#location_choices").removeAttr("disabled", "disabled");
+ $('#main_loading_image').hide();
+ }
+}
+
+function make_url(p) {
+ var base_url = document.location.href.concat("/");
+ return base_url.concat(p);
+}
+
+function get_ajax_obj() {
+ var ajaxRequest; // The variable that makes Ajax possible!
+
+ try{
+ // Opera 8.0+, Firefox, Safari
+ ajaxRequest = new XMLHttpRequest();
+ } catch (e){
+ // Internet Explorer Browsers
+ try{
+ ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {
+ try{
+ ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (e){
+ // Something went wrong
+ alert("Your browser broke!");
+ return false;
+ }
+ }
+ }
+ return ajaxRequest;
+}
+
+function std_error_handler(url, error_msg) {
+ var errorOpt = document.getElementById('error_status_text');
+ errorOpt.innerText = error_msg;
+ alert(error_msg);
+ disable_buttons(false, "Ready.")
+}
+
+function load_error_handler(url, error_msg) {
+ var errorOpt = document.getElementById('error_status_text');
+ errorOpt.innerText = error_msg;
+ $("#loading_image_div").hide();
+
+ error_msg = error_msg.concat(". Please refresh later.")
+ $("#error_status_text").html(error_msg);
+}
+
+
+function ajaxCallREST(url, func, error_func) {
+ var ajaxRequest = get_ajax_obj();
+ ajaxRequest.onreadystatechange = function(){
+ // We still need to write some code here
+ if(ajaxRequest.readyState == 4){
+ var error_msg = "";
+ if (ajaxRequest.status == 200){
+ var obj = eval('(' + ajaxRequest.responseText + ')');
+ if(obj.error_message != undefined) {
+ error_msg = obj.error_message;
+ }
+ else {
+ func(obj);
+ }
+ }
+ else {
+ error_msg = ajaxRequest.statusText
+ }
+ if (error_msg != "") {
+ error_func(url, error_msg);
+ }
+ }
+ }
+ ajaxRequest.open("GET", url, true);
+ ajaxRequest.send(null);
+}
+
+function loadDomainBox(obj) {
+ var selectOpt = document.getElementById('domain_list_choices');
+ selectOpt.options.length = 0;
+ for(var i=0; i< obj.domains.length; i++){
+ var newOpt = document.createElement('option');
+ newOpt.text = obj.domains[i].name;
+ newOpt.value = obj.domains[i].name;
+ selectOpt.add(newOpt);
+ }
+}
+
+function populateIaaSCb(obj) {
+ var selectOpt = document.getElementById('user_images_choices');
+ for(var i=0; i< obj.user_images.length; i++){
+ var newOpt = document.createElement('option');
+ newOpt.text = obj.user_images[i];
+ newOpt.value = obj.user_images[i];
+ selectOpt.add(newOpt);
+ }
+ var selectOpt = document.getElementById('common_images_choices');
+ for(var i=0; i< obj.common_images.length; i++){
+ var newOpt = document.createElement('option');
+ newOpt.text = obj.common_images[i];
+ newOpt.value = obj.common_images[i];
+ selectOpt.add(newOpt);
+ }
+}
+
+function ajaxPopulateInitial(url) {
+
+ var func = function(obj){
+ populateIaaSCb(obj);
+ loadDomainBox(obj);
+ $("#loading_image_div").hide();
+ $("#phantom_loaded_content").show();
+
+ }
+ ajaxCallREST(url, func, load_error_handler);
+}
+
+function clear_iaas_inputs() {
+ $("#common_images_choices").empty();
+ $("#user_images_choices").empty();
+}
+
+
+function populateIaaS() {
+ var u = make_url('get_iaas?cloud=');
+ var c = document.getElementById('location_choices');
+ u = u.concat(c.value);
+ var func = function(obj){
+ clear_iaas_inputs();
+ populateIaaSCb(obj);
+ disable_buttons(false);
+ }
+ disable_buttons(true);
+ ajaxCallREST(u, func, std_error_handler);
+}
+
+function ajaxGetDomains(url) {
+
+ var func = function(obj){
+ loadDomainBox(obj);
+ disable_buttons(false);
+ }
+ disable_buttons(true);
+ ajaxCallREST(url, func, std_error_handler);
+}
+
+function listAllDomains() {
+ var u = make_url('domain/list');
+ ajaxGetDomains(u);
+}
+
+function startDomain() {
+ var asgNameOpt = document.getElementById('domain_name_input');
+ var asgSizeOpt = document.getElementById('domain_size_input');
+ var allocOpt = document.getElementById('allocation_choices');
+ var locationOpt = document.getElementById('location_choices');
+ var userImageOpt = document.getElementById('user_images_choices');
+ var commonImageOpt = document.getElementById('common_images_choices');
+ var whichOpt = document.getElementById('user_choice_checked');
+
+ var common_check = "true";
+ if (whichOpt.checked) {
+ var image = userImageOpt.value;
+ var common_check = "false";
+ }
+ else {
+ var image = commonImageOpt.value;
+ }
+
+ var u = make_url('domain/start?');
+ u = u.concat('size=').concat(allocOpt.value);
+ u = u.concat('&name=').concat(asgNameOpt.value);
+ u = u.concat('&image=').concat(image);
+ u = u.concat('&cloud=').concat(locationOpt.value);
+ u = u.concat('&common=').concat(common_check);
+ u = u.concat('&desired_size=').concat(asgSizeOpt.value);
+
+ var func = function(obj){
+ listAllDomains();
+ }
+
+ disable_buttons(true, "Starting new domain ".concat(asgNameOpt.value).concat("..."));
+ ajaxCallREST(u, func, std_error_handler);
+}
+
+function deleteDomain() {
+
+ var domainChoiceOpt = document.getElementById('domain_list_choices');
+
+ if (domainChoiceOpt.value == "") {
+ alert("You must select a LaunchConfiguration to delete")
+ return;
+ }
+ var url = make_url('domain/delete?name=');
+ url = url.concat(domainChoiceOpt.value)
+
+ var rc = confirm('Are you sure you want to delete the Domain '.concat(domainChoiceOpt.value))
+ if (rc) {
+ var func = function(obj){
+ listAllDomains();
+ }
+ $("#domain_details_name").html("");
+ $("#instance_details").empty();
+ disable_buttons(true, "Terminating ".concat(domainChoiceOpt.value).concat("..."));
+ ajaxCallREST(url, func, std_error_handler);
+ }
+}
+
+function set_option_box(opt, name) {
+ for(var i=0; i < opt.options.length; i++) {
+ var x = opt.options[i];
+ if (x.text == name) {
+ opt.selectedIndex = i;
+
+ return true;
+ }
+ }
+ opt.selectedIndex = -1;
+ return false;
+}
+
+function loadDomainName() {
+
+ var domainListOpt = document.getElementById('domain_list_choices');
+ var url = make_url('domain/list?domain_name=');
+ url = url.concat(domainListOpt.value)
+
+ var func = function(obj) {
+ var nameOpt = document.getElementById('domain_name_input');
+ var desiredSizeOpt = document.getElementById('domain_size_input');
+
+ var domain = null;
+ var found = false;
+ for(var i=0; i< obj.domains.length; i++){
+ if (obj.domains[i].name == domainListOpt.value) {
+ domain = obj.domains[i];
+ found = true;
+ }
+ }
+
+ if (found == false){
+ return;
+ }
+
+ var locationOpt = document.getElementById('location_choices');
+ var allocOpt = document.getElementById('allocation_choices');
+ var userOpt = document.getElementById('user_images_choices');
+ var commonOpt = document.getElementById('common_images_choices');
+ var commonCheckOpt = document.getElementById('common_choice_checked');
+ var userCheckOpt = document.getElementById('user_choice_checked');
+
+ nameOpt.value = domain.name;
+ desiredSizeOpt.value = domain.desired_capacity;
+ set_option_box(allocOpt, domain.instance_type);
+ set_option_box(locationOpt, domain.cloudname);
+ var c_rc = set_option_box(commonOpt, domain.image_id);
+ var u_rc = set_option_box(userOpt, domain.image_id);
+ userCheckOpt.checked = u_rc;
+ commonCheckOpt.checked = c_rc;
+
+ $("#domain_details_name").html(domain.name.concat(" instance details."));
+
+ for(var i=0; i< domain.instances.length; i++) {
+ var instance = domain.instances[i];
+
+ var fields = new Array();
+ fields[0] = instance.lifecycle_state;
+ fields[1] = instance.cloud;
+ fields[2] = instance.health_status;
+ fields[3] = instance.hostname;
+ fields[4] = domain.image_id;
+
+ var li = $('<li></li>');
+ $("#instance_details").append(li);
+ var div = $('<div></div>').addClass('instance_status_div');
+ var h4 = $('<h4></h4>').addClass('instance_status_name');
+ h4.html(instance.instance_id);
+ var ul = $('<ul></ul>').addClass('instance_status_details_list');
+ li.append(div);
+ div.append(h4);
+ div.append(ul);
+
+ for(var j = 0; j < fields.length; j++) {
+ var subli = $('<li></li>').addClass('instance_status_details_item');
+ subli.html(fields[j]);
+ ul.append(subli);
+ }
+ disable_buttons(false);
+ }
+
+ }
+ var msg = "loading ".concat(domainListOpt.value).concat("...");
+ $("#domain_details_name").html(msg);
+ $("#instance_details").empty();
+ disable_buttons(true, msg);
+ ajaxCallREST(url, func, std_error_handler);
+}
+
+
+function loadPage(){
+ $("#error_status_text").html("Ready.");
+ $("#phantom_loaded_content").hide();
+ var u = make_url('get_initial?cloud=');
+ var c = document.getElementById('location_choices');
+ u = u.concat(c.value);
+ ajaxPopulateInitial(u);
+}
101 phantomweb/templates/base.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>{% block title %}Nimbus{% endblock %}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+ <link rel='stylesheet' type='text/css' media='all' href='http://www.nimbusproject.org/css/ee.css' />
+ <link rel='stylesheet' type='text/css' media='all' href='http://www.nimbusproject.org/css/navbar.css' />
+ <link rel='stylesheet' type='text/css' media='all' href='http://www.nimbusproject.org/css/site.css' />
+ <link rel='stylesheet' type='text/css' media='all' href='/static/css/phantom.css' />
+
+
+ <link rel="top" title="Nimbus v1.0 documentation" href="#" />
+
+ <script src="http://www.nimbusproject.org/js/jquery-1.3.1.js"></script>
+ <script src="http://www.nimbusproject.org/js/jquery.corner.js"></script>
+
+ {% block headscripts %}{% endblock %}
+
+</head>
+
+<body {% block bodytag %}{% endblock %}>
+
+
+<div id="nimbus_logo">
+<!--NIMBUS-->
+
+<img src="http://www.nimbusproject.org/images/nimbus_logo.png" />
+</div>
+
+<div id="container">
+ <div id="wrapper">
+ <div id="header">
+ <div id="navbar">
+ <ul id="nav">
+ <li class="main" id="home"><a href="/" title="Home"><span class="first">Home</span></a></li>
+ <li class="main" id="about"><a href="http://www.nimbusproject.org/about/" title="About">About Nimbus</a></li>
+
+ <li class="main" id="faq"><a href="/doc/nimbus/faq/" title="FAQ">FAQ</a></li>
+ <li class="main" id="docs"><a href="/doc/nimbus/" title="Documentation">Documentation</a></li>
+ <li class="main" id="software"><a href="http://www.nimbusproject.org/downloads/" title="Downloads">Download</a></li>
+ <li class="main" id="contact"><a href="http://www.nimbusproject.org/contact/" title="Community Resources">Community Resources</a></li>
+ <li class="main" id="pubs"><a href="http://www.nimbusproject.org/papers/" title="Publications">Publications</a></li>
+ <li class="last"><a href="http://www.nimbusproject.org/news/" title="News">News</a></li>
+ </ul>
+ </div>
+ </div>
+ <div id="content">
+ <div id="content_container">
+
+ <div id="blog">
+ <div class="content-wrapper">
+ <div class="doccontent">
+ <div class="document">
+ <div class="documentwrapper">
+ <div class="body">
+
+<ul>
+ <li class="phantom_links"><a href="/phantom">Home</a></li>
+</ul>
+{% block nimbus_body %}{% endblock %}
+
+ </div>
+ </div>
+ </div>
+ <div class="clearer"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <br clear="all" />
+ </div>
+ </div>
+ <div id="content_bottom">&nbsp;</div>
+</div>
+<br class="clear" />
+<div id="footer">
+
+ <ul>
+ <li><a href="/" title="Home"><span class="first">Home</span></a></li>
+ <li><a href="http://www.nimbusproject.org/about/" title="About">About Nimbus</a></li>
+ <li><a href="/doc/nimbus/faq/" title="FAQ">FAQ</a></li>
+ <li><a href="/doc/nimbus/" title="Documentation">Documentation</a></li>
+ <li><a href="http://www.nimbusproject.org/contact/" title="Community Resources">Community Resources</a></li>
+ <li><a href="http://www.nimbusproject.org/downloads/" title="Software">Download</a></li>
+
+ <li><a href="http://www.nimbusproject.org/pubs/" title="Publications">Publications</a></li>
+ <li class="last"><a href="http://www.nimbusproject.org/news/" title="News">News</a></li>
+ </ul>
+<p>&copy; University of Chicago</p>
+</div>
+
+<br class="clear" />
+
+<div id="bottom"><p>&nbsp;</p></div>
+
+</body>
+</html>
+
+
127 phantomweb/templates/phantom.html
@@ -0,0 +1,127 @@
+{% extends "../templates/base.html" %}
+{% block headscripts %}
+ <script src="/static/js/phantom.js"></script>
+{% endblock %}
+{% block bodytag %}onload="loadPage();"{% endblock %}
+{% block nimbus_body %}
+
+
+
+<div id="phantom_content">
+ <div id="loading_image_div">
+ <img src="static/images/loading1.gif"/>
+ </div>
+
+ <label id="error_status_text">Status</label>
+ <div id="phantom_loaded_content">
+ <div id="status_bar_div">
+ <img src="static/images/loading4.gif" id="main_loading_image"/>
+ </div>
+
+ <form name='myForm'>
+
+
+ <div id="main_combined_pane" class="form_pane">
+ <div class="phantom_section_div">
+ <div id="domain_name_div" class="phantom_domain_row">
+ <div class="combo_label">
+ <label id="domain_name_lbl" >Domain Name:</label>
+ </div>
+ <div class="combo_value">
+ <input type='text' id="domain_name_input" name='domain_name_text' label="domain_name_lbl"/>
+ </div>
+ </div>
+ </div>
+
+ <div class="phantom_section_div">
+ <div id="domain_size_div" class="phantom_domain_row">
+ <div class="combo_label">
+ <label id="domain_size_lbl" >Size:</label>
+ </div>
+ <div class="combo_value">
+ <input type='text' id="domain_size_input" name='domain_size_text' label="domain_size_lbl"/>
+ </div>
+ </div>
+ </div>
+
+
+ <div class="phantom_section_div">
+ <div class="phantom_domain_row">
+ <div class="combo_label">
+ <input id="user_choice_checked" class="image_radio" type="radio" label="user_image_label" name="common_or_user" value="user_image_selected" checked="checked"/>
+ <label id="user_image_label">User Image</label>
+ </div>
+ <div class="combo_value">
+ <select name="user_images" id="user_images_choices" class="imagechoice">
+ </select>
+ </div>
+ </div>
+
+ <div class="phantom_domain_row">
+ <div class="combo_label">
+ <input class="image_radio" id="common_choice_checked" type="radio" label="user_image_label" name="common_or_user" value="user_image_selected" checked="checked"/>
+ <label id="common_image_label">Common Image</label>
+ </div>
+ <div class="combo_value">
+ <select name="user_images" id="common_images_choices" class="imagechoice">
+ </select>
+ </div>
+ </div>
+ </div>
+
+ <div class="phantom_section_div">
+ <div class="phantom_domain_row">
+ <div class="combo_label">
+ <label id="allocation_choice_label" class="combo_label">Instance Size</label>
+ </div>
+ <div class="combo_value">
+ <select name="allocation" id="allocation_choices" class="imagechoice" label="allocation_choice_label">
+ {% for i in instance_types %}
+ <option>{{i}}</option>
+ {% endfor %}
+ </select>
+ </div>
+ </div>
+ </div>
+
+ <div class="phantom_section_div">
+ <div class="phantom_domain_row">
+ <div class="combo_label">
+ <label id="location_choice_label" class="combo_label">Cloud</label>
+ </div>
+ <div class="combo_value">
+ <select name="location" id="location_choices" class="imagechoice" label="location_choice_label" onchange="populateIaaS()">
+ {% for i in cloud_locations %}
+ <option>{{i}}</option>
+ {% endfor %}
+ </select>
+ </div>
+ </div>
+ </div>
+
+ <div id="lc_button_div" class="phantom_domain_row">
+ <input type="button" id="start_domain_button" class="formbutton" value="Start" onclick="startDomain()"/>
+ <input type="button" id="terminate_domain_button" class="formbutton" value="Terminate" onclick="deleteDomain()"/>
+ </div>
+
+ <div class="phantom_section_div">
+ <div id="domain_list_div" class="phantom_domain_row">
+ <select size="5" name="domain_list" id="domain_list_choices" class="imagechoice" onchange="loadDomainName()">
+ </select>
+ </div>
+ </div>
+
+ </div>
+
+ <div id="domain_details_pane" class="form_pane">
+ <input type="button" id="refresh_instances_button" class="formbutton" value="Refresh" onclick="loadDomainName()"/>
+ <h3 id="domain_details_name"></h3>
+ <div id="domain_details_list_div">
+ <ul id="instance_details">
+ </ul>
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+{% endblock %}
27 phantomweb/templates/registration/login.html
@@ -0,0 +1,27 @@
+{% extends "../templates/base.html" %}
+{% load url from future %}
+
+{% block nimbus_body %}
+
+{% if form.errors %}
+<p>Your username and password didn't match. Please try again.</p>
+{% endif %}
+
+<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
+{% csrf_token %}
+<table>
+<tr>
+ <td>{{ form.username.label_tag }}</td>
+ <td>{{ form.username }}</td>
+</tr>
+<tr>
+ <td>{{ form.password.label_tag }}</td>
+ <td>{{ form.password }}</td>
+</tr>
+</table>
+
+<input type="submit" value="login" />
+<input type="hidden" name="next" value="{{ next }}" />
+</form>
+
+{% endblock %}
20 phantomweb/urls.py
@@ -0,0 +1,20 @@
+from django.conf.urls.defaults import patterns, include, url
+from django.contrib import admin
+from django.contrib.auth.views import password_reset
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ url(r'^accounts/password/reset$', 'django.contrib.auth.views.password_change'),
+ url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ url(r'^admin/', include(admin.site.urls)),
+ (r'^accounts/login/$', 'django.contrib.auth.views.login'),
+ url(r'^phantom/get_iaas$', 'phantomweb.views.django_get_iaas_info'),
+ url(r'^phantom/get_initial$', 'phantomweb.views.django_get_initial_info'),
+ url(r'^phantom/domain/list$', 'phantomweb.views.django_list_domain'),
+ url(r'^phantom/domain/start$', 'phantomweb.views.django_start_domain'),
+ url(r'^phantom/domain/delete$', 'phantomweb.views.django_delete_domain'),
+ url(r'^phantom$', 'phantomweb.views.django_phantom'),
+)
150 phantomweb/util.py
@@ -0,0 +1,150 @@
+from django.template import Context, loader
+from django.http import HttpResponse
+from phantomweb.models import UserPhantomInfoDB, UserCloudInfoDB
+from phantomweb.phantom_web_exceptions import PhantomWebException
+from pyhantom.authz.simple_sql_db import SimpleSQL
+from phantomweb.models import PhantomInfoDB, DefaultCloudsDB
+
+def get_key_name():
+ return "nimbusphantom"
+
+class PhantomWebDecorator(object):
+
+ def __init__(self, func):
+ self.func = func
+
+
+ def __call__(self, *args,**kwargs):
+ try:
+ return self.func(*args,**kwargs)
+ except PhantomWebException, ex:
+ response_dict = {
+ 'error_message': ex.message,
+ }
+ return response_dict
+
+def render_template(fname, d):
+ t = loader.get_template(full_path)
+ c = Context(response_dict)
+ return HttpResponse(t.render(c))
+
+def get_cloud_objects(username):
+ clouds = UserCloudInfoDB.objects.filter(username=username)
+ return clouds
+
+def get_user_object(username):
+ phantom_l = UserPhantomInfoDB.objects.filter(username=username)
+ if not phantom_l:
+ return None
+ if len(phantom_l) > 1:
+ raise PhantomWebException("There are multiple users by the name %s. The admin must clean this." % (username))
+ return phantom_l[0]
+
+
+class UserCloudInfo(object):
+
+ def __init__(self, cloudname, username, iaas_key, iaas_secret, cloud_url):
+ self.cloudname = cloudname
+ self.username = username
+ self.iaas_key = iaas_key
+ self.iaas_secret = iaas_secret
+ self.cloud_url = cloud_url
+
+class UserObject(object):
+ pass
+
+class UserObjectDJangoDB(UserObject):
+
+ def __init__(self, username):
+ self.username = username
+ self.phantom_data = get_user_object(username)
+ # add the user if they do not exist in the DB
+ if not self.phantom_data:
+ self.phantom_data = UserPhantomInfoDB()
+ self.phantom_data.username = username
+ self.phantom_data.phantom_key = ""
+ self.phantom_data.phantom_secret = ""
+ self.phantom_data.phantom_url = ""
+ self.phantom_data.save()
+
+ self.phantom_key = None
+ self.phantom_secret = None
+ self._load_clouds()
+
+ def has_phantom_data(self):
+ return (self.phantom_data and self.phantom_data.phantom_key and self.phantom_data.phantom_secret and self.phantom_data.phantom_url)
+
+ def _load_clouds(self):
+ clouds = get_cloud_objects(self.username)
+ self.iaasclouds = {}
+ for c in clouds:
+ self.iaasclouds[c.cloudname] = c
+
+ def get_cloud(self, name):
+ if name not in self.iaasclouds:
+ raise PhantomWebException("No cloud named %s associated with the user" % (name))
+ return self.iaasclouds[name]
+
+ def persist(self):
+ self.phantom_data.save()
+
+ def change_cloud(self, name, url, key, secret):
+ if name not in self.iaasclouds:
+ raise PhantomWebException("%s is not a known cloud for user %s" % (name, self.username))
+ c = self.iaasclouds[name]
+ self._change_cloud(c, name, url, key, secret)
+
+ def change_or_add(self, name, url, key, secret):
+ if name not in self.iaasclouds:
+ cloud_info = UserCloudInfoDB()
+ else:
+ cloud_info = self.iaasclouds[name]
+ self._change_cloud(cloud_info, name, url, key, secret)
+
+ def _change_cloud(self, cloud_info, name, url, key, secret):
+ cloud_info.cloudname = name
+ cloud_info.username = self.username
+ cloud_info.iaas_key = key
+ cloud_info.iaas_secret = secret
+ cloud_info.cloud_url = url
+ cloud_info.save()
+
+ def add_cloud(self, name, url, key, secret):
+ cloud_info = UserCloudInfoDB()
+ self._change_cloud(name, url, key, secret)
+ self.iaasclouds[cloud_info.cloudname] = cloud_info
+
+
+class UserObjectMySQL(UserObject):
+
+ def __init__(self, username):
+
+ phantom_info_objects = PhantomInfoDB.objects.all()
+ if not phantom_info_objects:
+ raise PhantomWebException('The service is mis-configured. Please contact your sysadmin')
+
+ self.phantom_info = phantom_info_objects[0]
+ self._authz = SimpleSQL(self.phantom_info.dburl)
+ self._user_dbobject = self._authz.get_user_object_by_display_name(username)
+ if not self._user_dbobject:
+ raise PhantomWebException('This user is not associated with cloud user database. Please contact your sysadmin')
+ self._load_clouds()
+
+ def has_phantom_data(self):
+ return True
+
+ def _load_clouds(self):
+ clouds = DefaultCloudsDB.objects.all()
+ self.iaasclouds = {}
+ for c in clouds:
+ uci = UserCloudInfo(c.name, self._user_dbobject.display_name, self._user_dbobject.access_id, self._user_dbobject.secret_key, c.url)
+ self.iaasclouds[c.name] = uci
+
+ def get_cloud(self, name):
+ if name not in self.iaasclouds:
+ raise PhantomWebException("No cloud named %s associated with the user" % (name))
+ return self.iaasclouds[name]
+
+
+def get_user_object(username):
+ return UserObjectMySQL(username)
69 phantomweb/views.py
@@ -0,0 +1,69 @@
+# Cr# Create your views here.
+from django.conf.urls.defaults import patterns
+from django.core.urlresolvers import reverse
+from django.template import Context, loader
+import simplejson
+from django.http import HttpResponse, HttpResponseRedirect
+from django.contrib.auth.decorators import login_required
+from phantomweb.phantom_web_exceptions import PhantomWebException, PhantomRedirectException
+from phantomweb.util import PhantomWebDecorator, get_user_object
+from phantomweb.workload import delete_domain, phantom_main_html, start_domain, list_domains, get_iaas_info
+from django.contrib import admin
+
+
+@login_required
+def django_get_initial_info(request):
+ user_obj = get_user_object(request.user.username)
+ response_dict = get_iaas_info(request.GET, user_obj)
+ domain_dict = list_domains(request.GET, user_obj)
+ response_dict.update(domain_dict)
+ h = HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
+ return h
+
+@login_required
+def django_get_iaas_info(request):
+ user_obj = get_user_object(request.user.username)
+ response_dict = get_iaas_info(request.GET, user_obj)
+ h = HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
+ return h
+
+@login_required
+def django_list_domain(request):
+ user_obj = get_user_object(request.user.username)
+ response_dict = list_domains(request.GET, user_obj)
+ h = HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
+ return h
+
+@login_required
+def django_start_domain(request):
+ user_obj = get_user_object(request.user.username)
+ response_dict = start_domain(request.GET, user_obj)
+ h = HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
+ return h
+
+@login_required
+def django_delete_domain(request):
+ user_obj = get_user_object(request.user.username)
+ response_dict = delete_domain(request.GET, user_obj)
+ h = HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
+ return h
+
+@login_required
+def django_phantom(request):
+ user_obj = get_user_object(request.user.username)
+ try:
+ response_dict = phantom_main_html(request.GET, user_obj)
+ t = loader.get_template('../templates/phantom.html')
+ c = Context(response_dict)
+ except PhantomRedirectException, ex:
+ return HttpResponseRedirect(ex.redir)
+ return HttpResponse(t.render(c))
+
+
+class MyModelAdmin(admin.ModelAdmin):
+ def get_urls(self):
+ urls = super(MyModelAdmin, self).get_urls()
+ my_urls = patterns('',
+ (r'^my_view/$', self.admin_site.admin_view(self.my_view))
+ )
+ return my_urls + urls
178 phantomweb/workload.py
@@ -0,0 +1,178 @@
+import boto
+from boto.ec2.connection import EC2Connection
+from boto.regioninfo import RegionInfo
+import logging
+import urlparse
+import boto.ec2.autoscale
+from phantomweb.phantom_web_exceptions import PhantomWebException
+from phantomweb.util import PhantomWebDecorator, get_key_name
+
+def get_phantom_con(userobj):
+ url = userobj.phantom_info.phantom_url
+ uparts = urlparse.urlparse(url)
+ is_secure = uparts.scheme == 'https'
+ region = RegionInfo(uparts.hostname)
+ con = boto.ec2.autoscale.AutoScaleConnection(aws_access_key_id=userobj._user_dbobject.access_id, aws_secret_access_key=userobj._user_dbobject.secret_key, is_secure=is_secure, port=uparts.port, region=region)
+ con.host = uparts.hostname
+ return con
+
+def get_iaas_compute_con(iaas_cloud):
+ uparts = urlparse.urlparse(iaas_cloud.cloud_url)
+ is_secure = uparts.scheme == 'https'
+ ec2conn = EC2Connection(iaas_cloud.iaas_key, iaas_cloud.iaas_secret, host=uparts.hostname, port=uparts.port, is_secure=is_secure)
+ ec2conn.host = uparts.hostname
+ return ec2conn
+
+def _get_keys(ec2conn):
+ r = ec2conn.get_all_key_pairs()
+ rs = [k.name for k in r]
+ return rs
+
+@PhantomWebDecorator
+def get_iaas_info(request_params, userobj):
+
+ params = ['cloud',]
+ for p in params:
+ if p not in request_params:
+ raise PhantomWebDecorator('Missing parameter %s' % (p))
+
+ cloud_name = request_params['cloud']
+ iaas_cloud = userobj.get_cloud(cloud_name)
+
+ ec2conn = get_iaas_compute_con(iaas_cloud)
+ l = ec2conn.get_all_images()
+ common_images = [c.id for c in l if c.is_public]
+ user_images = [u.id for u in l if not u.is_public]
+
+ response_dict = {
+ 'name': 'hello',
+ 'user_images': user_images,
+ 'common_images': common_images,
+ }
+ return response_dict
+
+@PhantomWebDecorator
+def list_domains(request_params, userobj):
+ con = get_phantom_con(userobj)
+
+ domain_names = None
+ if 'domain_name' in request_params:
+ domain_name = request_params['domain_name']
+ domain_names = [domain_name,]
+ asgs = con.get_all_groups(names=domain_names)
+ return_asgs = []
+
+ for a in asgs:
+ ent = {}
+ ent['name'] = a.name
+ ent['desired_capacity'] = a.desired_capacity
+ lc_name = a.launch_config_name
+ lcs = con.get_all_launch_configurations(names=[lc_name,])
+ ent['cloudname'] = a.availability_zones[0]
+ if lcs:
+ lc = lcs[0]
+ ent['lc_name'] = lc.name
+ ent['image_id'] = lc.image_id
+ ent['key_name'] = lc.key_name
+ ent['instance_type'] = lc.instance_type
+ inst_list = []
+ for instance in a.instances:
+ i_d = {}
+ i_d['cloud'] = instance.availability_zone
+ i_d['health_status'] = instance.health_status
+ i_d['instance_id'] = instance.instance_id.strip()
+ i_d['lifecycle_state'] = instance.lifecycle_state
+ inst_list.append(i_d)
+ i_d['hostname'] = "unknown"
+
+ if i_d['instance_id']:
+ # look up more info with boto. this could be optimized for network communication
+ iaas_cloud = userobj.get_cloud(i_d['cloud'])
+ iaas_con = get_iaas_compute_con(iaas_cloud)
+ boto_insts = iaas_con.get_all_instances(instance_ids=[i_d['instance_id'],])
+ if boto_insts and boto_insts[0].instances:
+ boto_i = boto_insts[0].instances[0]
+ i_d['hostname'] = boto_i.dns_name
+
+ ent['instances'] = inst_list
+
+ return_asgs.append(ent)
+
+ response_dict = {
+ 'name': 'hello',
+ 'domains': return_asgs,
+ }
+ return response_dict
+
+
+def _find_or_create_config(con, size, image, keyname, common, lc_name):
+ lcs = con.get_all_launch_configurations(names=[lc_name,])
+ if not lcs:
+ lc = boto.ec2.autoscale.launchconfig.LaunchConfiguration(con, name=lc_name, image_id=image, key_name=keyname, security_groups='default', instance_type=size)
+ con.create_launch_configuration(lc)
+ return lc
+ return lcs[0]
+
+
+@PhantomWebDecorator
+def start_domain(request_params, userobj):
+ con = get_phantom_con(userobj)
+
+ params = ['size', 'name', 'image', 'cloud', 'common']
+ for p in params:
+ if p not in request_params:
+ raise PhantomWebDecorator('Missing parameter %s' % (p))
+
+ image_name = request_params['image']
+ size = request_params['size']
+ asg_name = request_params['name']
+ cloud = request_params['cloud']
+ common = request_params['common']
+ try:
+ desired_size = int(request_params['desired_size'])
+ except:
+ raise PhantomWebException('Please set the desired size to an integer')
+
+ lc_name = "WEB-%s-%s-%s" % (size, image_name, common)
+ key_name = get_key_name()
+
+ iaas_cloud = userobj.get_cloud(cloud)
+ ec2con = get_iaas_compute_con(iaas_cloud)
+ kps = _get_keys(ec2con)
+ if key_name not in kps:
+ raise PhantomWebException("The key name %s is not known. Please provide a public key in the settings section." % (key_name))
+
+ lc_name = "%s@%s" % (lc_name, cloud)
+ lc = _find_or_create_config(con, size, image_name, key_name, common, lc_name)
+ asg = boto.ec2.autoscale.group.AutoScalingGroup(launch_config=lc, connection=con, group_name=asg_name, availability_zones=[cloud], min_size=desired_size, max_size=desired_size)
+ con.create_auto_scaling_group(asg)
+ response_dict = {
+ 'Success': True,
+ }
+ return response_dict
+
+@PhantomWebDecorator
+def delete_domain(request_params, userobj):
+ con = get_phantom_con(userobj)
+
+ params = ['name']
+ for p in params:
+ if p not in request_params:
+ return None
+
+ asg_name = request_params['name']
+ con.delete_auto_scaling_group(asg_name)
+ response_dict = {
+ 'Success': True,
+ }
+ return response_dict
+
+
+def phantom_main_html(request_params, userobj):
+ instance_types = ["m1.small", "m1.large"]
+ cloud_locations = userobj.iaasclouds.keys()
+ response_dict = {
+ 'instance_types': instance_types,
+ 'cloud_locations': cloud_locations,
+ }
+ return response_dict
28 phantomweb/wsgi.py
@@ -0,0 +1,28 @@
+"""
+WSGI config for phantomweb project.
+
+This module contains the WSGI application used by Django's development server
+and any production WSGI deployments. It should expose a module-level variable
+named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
+this application via the ``WSGI_APPLICATION`` setting.
+
+Usually you will have the standard Django WSGI application here, but it also
+might make sense to replace the whole Django WSGI application with a custom one
+that later delegates to the Django one. For example, you could introduce WSGI
+middleware here, or combine a Django application with an application of another
+framework.
+
+"""
+import os
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "phantomweb.settings")
+
+# This application object is used by any WSGI server configured to use this
+# file. This includes Django's development server, if the WSGI_APPLICATION
+# setting points here.
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()
+
+# Apply WSGI middleware here.
+# from helloworld.wsgi import HelloWorldApplication
+# application = HelloWorldApplication(application)
Please sign in to comment.
Something went wrong with that request. Please try again.