Added django-nova to openstack-dashboard repo
This commit is contained in:
parent
e6c6059ec2
commit
d4a5bc97f3
@ -1,3 +1,4 @@
|
||||
.dashboard-venv
|
||||
local/dashboard_openstack.sqlite3
|
||||
local/local_settings.py
|
||||
django-nova/src/django_nova.egg-info
|
||||
openstack-dashboard/.dashboard-venv
|
||||
openstack-dashboard/local/dashboard_openstack.sqlite3
|
||||
openstack-dashboard/local/local_settings.py
|
||||
|
49
README
49
README
@ -1,49 +0,0 @@
|
||||
OpenStack Dashboard
|
||||
-------------------
|
||||
|
||||
The OpenStack Dashboard is a reference implementation of a Django site that
|
||||
uses the Django-Nova project to provide web based interactions with the
|
||||
OpenStack Nova cloud controller.
|
||||
|
||||
For more information about the Django-Nova project, please visit:
|
||||
|
||||
http://launchpad.net/django-nova
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
The first step is to obtain a local copy of the django-nova project:
|
||||
|
||||
$ mkdir django-nova
|
||||
$ cd django-nova
|
||||
$ bzr init-repo .
|
||||
$ bzr branch lp:django-nova/trunk
|
||||
|
||||
|
||||
Next we will create the virtualenv for local development. A tool is included to
|
||||
create one for you:
|
||||
|
||||
$ python tools/install_venv.py <path to django-nova/trunk>
|
||||
|
||||
|
||||
Now that the virtualenv is created, you need to configure your local
|
||||
environment. To do this, create a local_settings.py file in the local/
|
||||
directory. There is a local_settings.py.example file there that may be used
|
||||
as a template.
|
||||
|
||||
Finally, issue the django syncdb command:
|
||||
|
||||
$ tools/with_venv.sh dashboard/manage.py syncdb
|
||||
|
||||
If after you have specified the admin user the script appears to hang, it
|
||||
probably means the installation of Nova being referred to in local_settings.py
|
||||
is unavailable.
|
||||
|
||||
|
||||
If all is well you should now able to run the server locally:
|
||||
|
||||
$ tools/with_venv.sh dashboard/manage.py runserver
|
||||
|
||||
|
||||
|
176
django-nova/LICENSE
Normal file
176
django-nova/LICENSE
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
42
django-nova/README
Normal file
42
django-nova/README
Normal file
@ -0,0 +1,42 @@
|
||||
OpenStack Django-Nova
|
||||
---------------------
|
||||
|
||||
The Django-Nova project is a Django module that is used to provide web based
|
||||
interactions with the OpenStack Nova cloud controller.
|
||||
|
||||
There is a reference implementation that uses this module located at:
|
||||
|
||||
http://launchpad.net/openstack-dashboard
|
||||
|
||||
It is highly recommended that you make use of this reference implementation
|
||||
so that changes you make can be visualized effectively and are consistent.
|
||||
Using this reference implementation as a development environment will greatly
|
||||
simplify development of the django-nova module.
|
||||
|
||||
Of course, if you are developing your own Django site using django-nova, then
|
||||
you can disregard this advice.
|
||||
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
Django-Nova uses Buildout (http://www.buildout.org/) to manage local
|
||||
development. To configure your local Buildout environment:
|
||||
|
||||
$ python bootstrap.py
|
||||
$ bin/buildout
|
||||
|
||||
This will install all the dependencies of django-nova and provide some useful
|
||||
scripts in the bin/ directory:
|
||||
|
||||
bin/python provides a python shell for the current buildout.
|
||||
bin/django provides django functions for the current buildout.
|
||||
|
||||
|
||||
You should now be able to run unit tests as follows:
|
||||
|
||||
$ bin/django test
|
||||
|
||||
|
||||
|
260
django-nova/bootstrap.py
Normal file
260
django-nova/bootstrap.py
Normal file
@ -0,0 +1,260 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (c) 2006 Zope Foundation and Contributors.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# This software is subject to the provisions of the Zope Public License,
|
||||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
|
||||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
|
||||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE.
|
||||
#
|
||||
##############################################################################
|
||||
"""Bootstrap a buildout-based project
|
||||
|
||||
Simply run this script in a directory containing a buildout.cfg.
|
||||
The script accepts buildout command-line options, so you can
|
||||
use the -c option to specify an alternate configuration file.
|
||||
"""
|
||||
|
||||
import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
|
||||
from optparse import OptionParser
|
||||
|
||||
if sys.platform == 'win32':
|
||||
def quote(c):
|
||||
if ' ' in c:
|
||||
return '"%s"' % c # work around spawn lamosity on windows
|
||||
else:
|
||||
return c
|
||||
else:
|
||||
quote = str
|
||||
|
||||
# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
|
||||
stdout, stderr = subprocess.Popen(
|
||||
[sys.executable, '-Sc',
|
||||
'try:\n'
|
||||
' import ConfigParser\n'
|
||||
'except ImportError:\n'
|
||||
' print 1\n'
|
||||
'else:\n'
|
||||
' print 0\n'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
has_broken_dash_S = bool(int(stdout.strip()))
|
||||
|
||||
# In order to be more robust in the face of system Pythons, we want to
|
||||
# run without site-packages loaded. This is somewhat tricky, in
|
||||
# particular because Python 2.6's distutils imports site, so starting
|
||||
# with the -S flag is not sufficient. However, we'll start with that:
|
||||
if not has_broken_dash_S and 'site' in sys.modules:
|
||||
# We will restart with python -S.
|
||||
args = sys.argv[:]
|
||||
args[0:0] = [sys.executable, '-S']
|
||||
args = map(quote, args)
|
||||
os.execv(sys.executable, args)
|
||||
# Now we are running with -S. We'll get the clean sys.path, import site
|
||||
# because distutils will do it later, and then reset the path and clean
|
||||
# out any namespace packages from site-packages that might have been
|
||||
# loaded by .pth files.
|
||||
clean_path = sys.path[:]
|
||||
import site
|
||||
sys.path[:] = clean_path
|
||||
for k, v in sys.modules.items():
|
||||
if k in ('setuptools', 'pkg_resources') or (
|
||||
hasattr(v, '__path__') and
|
||||
len(v.__path__)==1 and
|
||||
not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
|
||||
# This is a namespace package. Remove it.
|
||||
sys.modules.pop(k)
|
||||
|
||||
is_jython = sys.platform.startswith('java')
|
||||
|
||||
setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
|
||||
distribute_source = 'http://python-distribute.org/distribute_setup.py'
|
||||
|
||||
# parsing arguments
|
||||
def normalize_to_url(option, opt_str, value, parser):
|
||||
if value:
|
||||
if '://' not in value: # It doesn't smell like a URL.
|
||||
value = 'file://%s' % (
|
||||
urllib.pathname2url(
|
||||
os.path.abspath(os.path.expanduser(value))),)
|
||||
if opt_str == '--download-base' and not value.endswith('/'):
|
||||
# Download base needs a trailing slash to make the world happy.
|
||||
value += '/'
|
||||
else:
|
||||
value = None
|
||||
name = opt_str[2:].replace('-', '_')
|
||||
setattr(parser.values, name, value)
|
||||
|
||||
usage = '''\
|
||||
[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
|
||||
|
||||
Bootstraps a buildout-based project.
|
||||
|
||||
Simply run this script in a directory containing a buildout.cfg, using the
|
||||
Python that you want bin/buildout to use.
|
||||
|
||||
Note that by using --setup-source and --download-base to point to
|
||||
local resources, you can keep this script from going over the network.
|
||||
'''
|
||||
|
||||
parser = OptionParser(usage=usage)
|
||||
parser.add_option("-v", "--version", dest="version",
|
||||
help="use a specific zc.buildout version")
|
||||
parser.add_option("-d", "--distribute",
|
||||
action="store_true", dest="use_distribute", default=False,
|
||||
help="Use Distribute rather than Setuptools.")
|
||||
parser.add_option("--setup-source", action="callback", dest="setup_source",
|
||||
callback=normalize_to_url, nargs=1, type="string",
|
||||
help=("Specify a URL or file location for the setup file. "
|
||||
"If you use Setuptools, this will default to " +
|
||||
setuptools_source + "; if you use Distribute, this "
|
||||
"will default to " + distribute_source +"."))
|
||||
parser.add_option("--download-base", action="callback", dest="download_base",
|
||||
callback=normalize_to_url, nargs=1, type="string",
|
||||
help=("Specify a URL or directory for downloading "
|
||||
"zc.buildout and either Setuptools or Distribute. "
|
||||
"Defaults to PyPI."))
|
||||
parser.add_option("--eggs",
|
||||
help=("Specify a directory for storing eggs. Defaults to "
|
||||
"a temporary directory that is deleted when the "
|
||||
"bootstrap script completes."))
|
||||
parser.add_option("-t", "--accept-buildout-test-releases",
|
||||
dest='accept_buildout_test_releases',
|
||||
action="store_true", default=False,
|
||||
help=("Normally, if you do not specify a --version, the "
|
||||
"bootstrap script and buildout gets the newest "
|
||||
"*final* versions of zc.buildout and its recipes and "
|
||||
"extensions for you. If you use this flag, "
|
||||
"bootstrap and buildout will get the newest releases "
|
||||
"even if they are alphas or betas."))
|
||||
parser.add_option("-c", None, action="store", dest="config_file",
|
||||
help=("Specify the path to the buildout configuration "
|
||||
"file to be used."))
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
# if -c was provided, we push it back into args for buildout's main function
|
||||
if options.config_file is not None:
|
||||
args += ['-c', options.config_file]
|
||||
|
||||
if options.eggs:
|
||||
eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
|
||||
else:
|
||||
eggs_dir = tempfile.mkdtemp()
|
||||
|
||||
if options.setup_source is None:
|
||||
if options.use_distribute:
|
||||
options.setup_source = distribute_source
|
||||
else:
|
||||
options.setup_source = setuptools_source
|
||||
|
||||
if options.accept_buildout_test_releases:
|
||||
args.append('buildout:accept-buildout-test-releases=true')
|
||||
args.append('bootstrap')
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
import setuptools # A flag. Sometimes pkg_resources is installed alone.
|
||||
if not hasattr(pkg_resources, '_distribute'):
|
||||
raise ImportError
|
||||
except ImportError:
|
||||
ez_code = urllib2.urlopen(
|
||||
options.setup_source).read().replace('\r\n', '\n')
|
||||
ez = {}
|
||||
exec ez_code in ez
|
||||
setup_args = dict(to_dir=eggs_dir, download_delay=0)
|
||||
if options.download_base:
|
||||
setup_args['download_base'] = options.download_base
|
||||
if options.use_distribute:
|
||||
setup_args['no_fake'] = True
|
||||
ez['use_setuptools'](**setup_args)
|
||||
if 'pkg_resources' in sys.modules:
|
||||
reload(sys.modules['pkg_resources'])
|
||||
import pkg_resources
|
||||
# This does not (always?) update the default working set. We will
|
||||
# do it.
|
||||
for path in sys.path:
|
||||
if path not in pkg_resources.working_set.entries:
|
||||
pkg_resources.working_set.add_entry(path)
|
||||
|
||||
cmd = [quote(sys.executable),
|
||||
'-c',
|
||||
quote('from setuptools.command.easy_install import main; main()'),
|
||||
'-mqNxd',
|
||||
quote(eggs_dir)]
|
||||
|
||||
if not has_broken_dash_S:
|
||||
cmd.insert(1, '-S')
|
||||
|
||||
find_links = options.download_base
|
||||
if not find_links:
|
||||
find_links = os.environ.get('bootstrap-testing-find-links')
|
||||
if find_links:
|
||||
cmd.extend(['-f', quote(find_links)])
|
||||
|
||||
if options.use_distribute:
|
||||
setup_requirement = 'distribute'
|
||||
else:
|
||||
setup_requirement = 'setuptools'
|
||||
ws = pkg_resources.working_set
|
||||
setup_requirement_path = ws.find(
|
||||
pkg_resources.Requirement.parse(setup_requirement)).location
|
||||
env = dict(
|
||||
os.environ,
|
||||
PYTHONPATH=setup_requirement_path)
|
||||
|
||||
requirement = 'zc.buildout'
|
||||
version = options.version
|
||||
if version is None and not options.accept_buildout_test_releases:
|
||||
# Figure out the most recent final version of zc.buildout.
|
||||
import setuptools.package_index
|
||||
_final_parts = '*final-', '*final'
|
||||
def _final_version(parsed_version):
|
||||
for part in parsed_version:
|
||||
if (part[:1] == '*') and (part not in _final_parts):
|
||||
return False
|
||||
return True
|
||||
index = setuptools.package_index.PackageIndex(
|
||||
search_path=[setup_requirement_path])
|
||||
if find_links:
|
||||
index.add_find_links((find_links,))
|
||||
req = pkg_resources.Requirement.parse(requirement)
|
||||
if index.obtain(req) is not None:
|
||||
best = []
|
||||
bestv = None
|
||||
for dist in index[req.project_name]:
|
||||
distv = dist.parsed_version
|
||||
if _final_version(distv):
|
||||
if bestv is None or distv > bestv:
|
||||
best = [dist]
|
||||
bestv = distv
|
||||
elif distv == bestv:
|
||||
best.append(dist)
|
||||
if best:
|
||||
best.sort()
|
||||
version = best[-1].version
|
||||
if version:
|
||||
requirement = '=='.join((requirement, version))
|
||||
cmd.append(requirement)
|
||||
|
||||
if is_jython:
|
||||
import subprocess
|
||||
exitcode = subprocess.Popen(cmd, env=env).wait()
|
||||
else: # Windows prefers this, apparently; otherwise we would prefer subprocess
|
||||
exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
|
||||
if exitcode != 0:
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
print ("An error occurred when trying to install zc.buildout. "
|
||||
"Look above this message for any errors that "
|
||||
"were output by easy_install.")
|
||||
sys.exit(exitcode)
|
||||
|
||||
ws.add_entry(eggs_dir)
|
||||
ws.require(requirement)
|
||||
import zc.buildout.buildout
|
||||
zc.buildout.buildout.main(args)
|
||||
if not options.eggs: # clean up temporary egg directory
|
||||
shutil.rmtree(eggs_dir)
|
19
django-nova/buildout.cfg
Normal file
19
django-nova/buildout.cfg
Normal file
@ -0,0 +1,19 @@
|
||||
[buildout]
|
||||
parts = python django
|
||||
develop = .
|
||||
eggs = django-nova
|
||||
|
||||
[python]
|
||||
recipe = zc.recipe.egg
|
||||
interpreter = python
|
||||
eggs = ${buildout:eggs}
|
||||
|
||||
[django]
|
||||
recipe = djangorecipe
|
||||
version = 1.2.4
|
||||
project = django_nova
|
||||
projectegg = django_nova
|
||||
settings = testsettings
|
||||
test = django_nova
|
||||
eggs = ${buildout:eggs}
|
||||
|
29
django-nova/setup.py
Normal file
29
django-nova/setup.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
def read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
setup(
|
||||
name = "django-nova",
|
||||
version = "0.1",
|
||||
url = 'https://launchpad.net/django-nova/',
|
||||
license = 'Apache 2.0',
|
||||
description = "A Django interface for OpenStack Nova.",
|
||||
long_description = read('README'),
|
||||
author = 'Devin Carlen',
|
||||
author_email = 'devin.carlen@gmail.com',
|
||||
packages = find_packages('src'),
|
||||
package_dir = {'': 'src'},
|
||||
install_requires = ['setuptools', 'boto==1.9b', 'mox>=0.5.0'],
|
||||
classifiers = [
|
||||
'Development Status :: 4 - Beta',
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: Apache License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
]
|
||||
)
|
||||
|
498
django-nova/src/django_nova/adminclient.py
Normal file
498
django-nova/src/django_nova/adminclient.py
Normal file
@ -0,0 +1,498 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Nova User API client library.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import boto
|
||||
import boto.exception
|
||||
import httplib
|
||||
import re
|
||||
import string
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
|
||||
|
||||
DEFAULT_CLC_URL='http://127.0.0.1:8773'
|
||||
DEFAULT_REGION='nova'
|
||||
DEFAULT_ACCESS_KEY='admin'
|
||||
DEFAULT_SECRET_KEY='admin'
|
||||
|
||||
|
||||
class UserInfo(object):
|
||||
"""
|
||||
Information about a Nova user, as parsed through SAX
|
||||
fields include:
|
||||
username
|
||||
accesskey
|
||||
secretkey
|
||||
|
||||
and an optional field containing a zip with X509 cert & rc
|
||||
file
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None, username=None, endpoint=None):
|
||||
self.connection = connection
|
||||
self.username = username
|
||||
self.endpoint = endpoint
|
||||
|
||||
def __repr__(self):
|
||||
return 'UserInfo:%s' % self.username
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'username':
|
||||
self.username = str(value)
|
||||
elif name == 'file':
|
||||
self.file = base64.b64decode(str(value))
|
||||
elif name == 'accesskey':
|
||||
self.accesskey = str(value)
|
||||
elif name == 'secretkey':
|
||||
self.secretkey = str(value)
|
||||
|
||||
|
||||
class UserRole(object):
|
||||
"""
|
||||
Information about a Nova user's role, as parsed through SAX.
|
||||
Fields include:
|
||||
role
|
||||
"""
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.role = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'UserRole:%s' % self.role
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'role':
|
||||
self.role = value
|
||||
else:
|
||||
setattr(self, name, str(value))
|
||||
|
||||
|
||||
class ProjectInfo(object):
|
||||
"""
|
||||
Information about a Nova project, as parsed through SAX
|
||||
Fields include:
|
||||
projectname
|
||||
description
|
||||
projectManagerId
|
||||
memberIds
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.projectname = None
|
||||
self.description = None
|
||||
self.projectManagerId = None
|
||||
self.memberIds = []
|
||||
|
||||
def __repr__(self):
|
||||
return 'ProjectInfo:%s' % self.projectname
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'projectname':
|
||||
self.projectname = value
|
||||
elif name == 'description':
|
||||
self.description = value
|
||||
elif name == 'projectManagerId':
|
||||
self.projectManagerId = value
|
||||
elif name == 'memberId':
|
||||
self.memberIds.append(value)
|
||||
else:
|
||||
setattr(self, name, str(value))
|
||||
|
||||
|
||||
class ProjectMember(object):
|
||||
"""
|
||||
Information about a Nova project member, as parsed through SAX.
|
||||
Fields include:
|
||||
memberId
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.memberId = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'ProjectMember:%s' % self.memberId
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'member':
|
||||
self.memberId = value
|
||||
else:
|
||||
setattr(self, name, str(value))
|
||||
|
||||
|
||||
class HostInfo(object):
|
||||
"""
|
||||
Information about a Nova Host, as parsed through SAX:
|
||||
Hostname
|
||||
Compute Service Status
|
||||
Volume Service Status
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.hostname = None
|
||||
self.compute = None
|
||||
self.volume = None
|
||||
self.instance_count = 0
|
||||
self.volume_count = 0
|
||||
|
||||
def __repr__(self):
|
||||
return 'Host:%s' % self.hostname
|
||||
|
||||
# this is needed by the sax parser, so ignore the ugly name
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
# this is needed by the sax parser, so ignore the ugly name
|
||||
def endElement(self, name, value, connection):
|
||||
fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name))
|
||||
setattr(self, fixed_name, value)
|
||||
|
||||
|
||||
class Vpn(object):
|
||||
"""
|
||||
Information about a Vpn, as parsed through SAX
|
||||
fields include:
|
||||
instance_id
|
||||
project_id
|
||||
public_ip
|
||||
public_port
|
||||
created_at
|
||||
internal_ip
|
||||
state
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.instance_id = None
|
||||
self.project_id = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vpn:%s:%s' % (self.project_id, self.instance_id)
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == 'instanceId':
|
||||
self.instance_id = str(value)
|
||||
elif name == 'projectId':
|
||||
self.project_id = str(value)
|
||||
elif name == 'publicIp':
|
||||
self.public_ip = str(value)
|
||||
elif name == 'publicPort':
|
||||
self.public_port = str(value)
|
||||
elif name == 'createdAt':
|
||||
self.created_at = str(value)
|
||||
elif name == 'internalIp':
|
||||
self.internal_ip = str(value)
|
||||
else:
|
||||
setattr(self, name, str(value))
|
||||
|
||||
|
||||
class InstanceType(object):
|
||||
"""
|
||||
Information about a Nova instance type, as parsed through SAX.
|
||||
|
||||
**Fields include**
|
||||
|
||||
* name
|
||||
* vcpus
|
||||
* disk_gb
|
||||
* memory_mb
|
||||
* flavor_id
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.name = None
|
||||
self.vcpus = None
|
||||
self.disk_gb = None
|
||||
self.memory_mb = None
|
||||
self.flavor_id = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'InstanceType:%s' % self.name
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
if name == "memoryMb":
|
||||
self.memory_mb = str(value)
|
||||
elif name == "flavorId":
|
||||
self.flavor_id = str(value)
|
||||
elif name == "diskGb":
|
||||
self.disk_gb = str(value)
|
||||
else:
|
||||
setattr(self, name, str(value))
|
||||
|
||||
|
||||
class StatusResponse(object):
|
||||
def __init__(self, connection=None):
|
||||
self.connection = connection
|
||||
self.status = None
|
||||
|
||||
def __repr__(self):
|
||||
return 'Status:%s' % self.status
|
||||
|
||||
def startElement(self, name, attrs, connection):
|
||||
return None
|
||||
|
||||
def endElement(self, name, value, connection):
|
||||
setattr(self, name, str(value))
|
||||
|
||||
|
||||
class NovaAdminClient(object):
|
||||
def __init__(self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION,
|
||||
access_key=DEFAULT_ACCESS_KEY, secret_key=DEFAULT_SECRET_KEY,
|
||||
**kwargs):
|
||||
parts = self.split_clc_url(clc_url)
|
||||
|
||||
self.clc_url = clc_url
|
||||
self.region = region
|
||||
self.access = access_key
|
||||
self.secret = secret_key
|
||||
self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
is_secure=parts['is_secure'],
|
||||
region=RegionInfo(None,
|
||||
region,
|
||||
parts['ip']),
|
||||
port=parts['port'],
|
||||
path='/services/Admin',
|
||||
**kwargs)
|
||||
self.apiconn.APIVersion = 'nova'
|
||||
|
||||
def connection_for(self, username, project, clc_url=None, region=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Returns a boto ec2 connection for the given username.
|
||||
"""
|
||||
if not clc_url:
|
||||
clc_url = self.clc_url
|
||||
if not region:
|
||||
region = self.region
|
||||
parts = self.split_clc_url(clc_url)
|
||||
user = self.get_user(username)
|
||||
access_key = '%s:%s' % (user.accesskey, project)
|
||||
return boto.connect_ec2(aws_access_key_id=access_key,
|
||||
aws_secret_access_key=user.secretkey,
|
||||
is_secure=parts['is_secure'],
|
||||
region=RegionInfo(None,
|
||||
self.region,
|
||||
parts['ip']),
|
||||
port=parts['port'],
|
||||
path='/services/Cloud',
|
||||
**kwargs)
|
||||
|
||||
def split_clc_url(self, clc_url):
|
||||
"""
|
||||
Splits a cloud controller endpoint url.
|
||||
"""
|
||||
parts = httplib.urlsplit(clc_url)
|
||||
is_secure = parts.scheme == 'https'
|
||||
ip, port = parts.netloc.split(':')
|
||||
return {'ip': ip, 'port': int(port), 'is_secure': is_secure}
|
||||
|
||||
def get_users(self):
|
||||
""" grabs the list of all users """
|
||||
return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)])
|
||||
|
||||
def get_user(self, name):
|
||||
""" grab a single user by name """
|
||||
try:
|
||||
return self.apiconn.get_object('DescribeUser', {'Name': name}, UserInfo)
|
||||
except boto.exception.BotoServerError, e:
|
||||
if e.status == 400 and e.error_code == 'NotFound':
|
||||
return None
|
||||
raise
|
||||
|
||||
def has_user(self, username):
|
||||
""" determine if user exists """
|
||||
return self.get_user(username) != None
|
||||
|
||||
def create_user(self, username):
|
||||
""" creates a new user, returning the userinfo object with access/secret """
|
||||
return self.apiconn.get_object('RegisterUser', {'Name': username}, UserInfo)
|
||||
|
||||
def delete_user(self, username):
|
||||
""" deletes a user """
|
||||
return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo)
|
||||
|
||||
def get_roles(self, project_roles=True):
|
||||
"""Returns a list of available roles."""
|
||||
return self.apiconn.get_list('DescribeRoles',
|
||||
{'ProjectRoles': project_roles},
|
||||
[('item', UserRole)])
|
||||
|
||||
def get_user_roles(self, user, project=None):
|
||||
"""Returns a list of roles for the given user.
|
||||
Omitting project will return any global roles that the user has.
|
||||
Specifying project will return only project specific roles.
|
||||
"""
|
||||
params = {'User':user}
|
||||
if project:
|
||||
params['Project'] = project
|
||||
return self.apiconn.get_list('DescribeUserRoles',
|
||||
params,
|
||||
[('item', UserRole)])
|
||||
|
||||
def add_user_role(self, user, role, project=None):
|
||||
"""
|
||||
Add a role to a user either globally or for a specific project.
|
||||
"""
|
||||
return self.modify_user_role(user, role, project=project,
|
||||
operation='add')
|
||||
|
||||
def remove_user_role(self, user, role, project=None):
|
||||
"""
|
||||
Remove a role from a user either globally or for a specific project.
|
||||
"""
|
||||
return self.modify_user_role(user, role, project=project,
|
||||
operation='remove')
|
||||
|
||||
def modify_user_role(self, user, role, project=None, operation='add',
|
||||
**kwargs):
|
||||
"""
|
||||
Add or remove a role for a user and project.
|
||||
"""
|
||||
params = {'User': user,
|
||||
'Role': role,
|
||||
'Project': project,
|
||||
'Operation': operation}
|
||||
return self.apiconn.get_status('ModifyUserRole', params)
|
||||
|
||||
def get_projects(self, user=None):
|
||||
"""
|
||||
Returns a list of all projects.
|
||||
"""
|
||||
if user:
|
||||
params = {'User': user}
|
||||
else:
|
||||
params = {}
|
||||
return self.apiconn.get_list('DescribeProjects',
|
||||
params,
|
||||
[('item', ProjectInfo)])
|
||||
|
||||
def get_project(self, name):
|
||||
"""
|
||||
Returns a single project with the specified name.
|
||||
"""
|
||||
project = self.apiconn.get_object('DescribeProject',
|
||||
{'Name': name},
|
||||
ProjectInfo)
|
||||
|
||||
if project.projectname != None:
|
||||
return project
|
||||
|
||||
def create_project(self, projectname, manager_user, description=None,
|
||||
member_users=None):
|
||||
"""
|
||||
Creates a new project.
|
||||
"""
|
||||
params = {'Name': projectname,
|
||||
'ManagerUser': manager_user,
|
||||
'Description': description,
|
||||
'MemberUsers': member_users}
|
||||
return self.apiconn.get_object('RegisterProject', params, ProjectInfo)
|
||||
|
||||
def delete_project(self, projectname):
|
||||
"""
|
||||
Permanently deletes the specified project.
|
||||
"""
|
||||
return self.apiconn.get_object('DeregisterProject',
|
||||
{'Name': projectname},
|
||||
ProjectInfo)
|
||||
|
||||
def get_project_members(self, name):
|
||||
"""
|
||||
Returns a list of members of a project.
|
||||
"""
|
||||
return self.apiconn.get_list('DescribeProjectMembers',
|
||||
{'Name': name},
|
||||
[('item', ProjectMember)])
|
||||
|
||||
def add_project_member(self, user, project):
|
||||
"""
|
||||
Adds a user to a project.
|
||||
"""
|
||||
return self.modify_project_member(user, project, operation='add')
|
||||
|
||||
def remove_project_member(self, user, project):
|
||||
"""
|
||||
Removes a user from a project.
|
||||
"""
|
||||
return self.modify_project_member(user, project, operation='remove')
|
||||
|
||||
def modify_project_member(self, user, project, operation='add'):
|
||||
"""
|
||||
Adds or removes a user from a project.
|
||||
"""
|
||||
params = {'User': user,
|
||||
'Project': project,
|
||||
'Operation': operation}
|
||||
return self.apiconn.get_status('ModifyProjectMember', params)
|
||||
|
||||
def get_zip(self, user, project):
|
||||
"""
|
||||
Returns the content of a zip file containing novarc and access credentials.
|
||||
"""
|
||||
params = {'Name': user, 'Project': project}
|
||||
zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo)
|
||||
return zip.file
|
||||
|
||||
def start_vpn(self, project):
|
||||
"""
|
||||
Starts the vpn for a user
|
||||
"""
|
||||
return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn)
|
||||
|
||||
def get_vpns(self):
|
||||
"""Return a list of vpn with project name"""
|
||||
return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)])
|
||||
|
||||
def get_hosts(self):
|
||||
return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)])
|
||||
|
||||
def get_instance_types(self):
|
||||
"""Grabs the list of all users."""
|
||||
return self.apiconn.get_list('DescribeInstanceTypes', {},
|
||||
[('item', InstanceType)])
|
||||
|
||||
def disable_project_credentials(self, project):
|
||||
"""Revoke project credentials and kill the cloudpipe/vpn instance"""
|
||||
return self.apiconn.get_object('DisableProjectCredentials',
|
||||
{'Project': project}, StatusResponse)
|
38
django-nova/src/django_nova/connection.py
Normal file
38
django-nova/src/django_nova/connection.py
Normal file
@ -0,0 +1,38 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Manage connections to Nova's admin API.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django_nova import adminclient
|
||||
|
||||
|
||||
def get_nova_admin_connection():
|
||||
"""
|
||||
Returns a Nova administration connection.
|
||||
"""
|
||||
return adminclient.NovaAdminClient (
|
||||
clc_url=settings.NOVA_DEFAULT_ENDPOINT,
|
||||
region=settings.NOVA_DEFAULT_REGION,
|
||||
access_key=settings.NOVA_ACCESS_KEY,
|
||||
secret_key=settings.NOVA_SECRET_KEY
|
||||
)
|
||||
|
||||
|
||||
|
95
django-nova/src/django_nova/exceptions.py
Normal file
95
django-nova/src/django_nova/exceptions.py
Normal file
@ -0,0 +1,95 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Better wrappers for errors from Nova's admin api.
|
||||
"""
|
||||
|
||||
import boto.exception
|
||||
from django.shortcuts import redirect
|
||||
from django.core import exceptions as core_exceptions
|
||||
|
||||
|
||||
class NovaServerError(Exception):
|
||||
"""
|
||||
Consumes a BotoServerError and gives more meaningful errors.
|
||||
"""
|
||||
def __init__(self, ec2error):
|
||||
self.status = ec2error.status
|
||||
self.message = ec2error.reason
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class NovaApiError(Exception):
|
||||
"""
|
||||
Used when Nova returns a 400 Bad Request status.
|
||||
"""
|
||||
def __init__(self, ec2error):
|
||||
self.message = ec2error.error_message
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class NovaUnavailableError(NovaServerError):
|
||||
"""
|
||||
Used when Nova returns a 503 Service Unavailable status.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NovaUnauthorizedError(core_exceptions.PermissionDenied):
|
||||
"""
|
||||
Used when Nova returns a 401 Not Authorized status.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def wrap_nova_error(func):
|
||||
"""
|
||||
Used to decorate a function that interacts with boto. It will catch
|
||||
and convert boto server errors and reraise as a more specific nova error.
|
||||
"""
|
||||
def decorator(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except boto.exception.BotoServerError, e:
|
||||
if e.status == 400 and e.error_code == 'ApiError':
|
||||
raise NovaApiError(e)
|
||||
elif e.status == 401:
|
||||
raise NovaUnauthorizedError()
|
||||
elif e.status == 503:
|
||||
raise NovaUnavailableError(e)
|
||||
raise NovaServerError(e)
|
||||
return decorator
|
||||
|
||||
|
||||
def handle_nova_error(func):
|
||||
"""
|
||||
Decorator for handling nova errors in a generalized way.
|
||||
"""
|
||||
def decorator(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except NovaUnavailableError:
|
||||
return redirect('nova_unavailable')
|
||||
return decorator
|
||||
|
||||
|
262
django-nova/src/django_nova/forms.py
Normal file
262
django-nova/src/django_nova/forms.py
Normal file
@ -0,0 +1,262 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Forms used by various views.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
from django_nova.exceptions import wrap_nova_error
|
||||
|
||||
|
||||
# TODO: Store this in settings.
|
||||
MAX_VOLUME_SIZE = 100
|
||||
|
||||
alphanumeric_re = re.compile(r'^\w+$')
|
||||
|
||||
|
||||
@wrap_nova_error
|
||||
def get_instance_type_choices():
|
||||
"""
|
||||
Returns list of instance types from nova admin api
|
||||
"""
|
||||
nova = get_nova_admin_connection()
|
||||
instance_types = nova.get_instance_types()
|
||||
rv = []
|
||||
for t in instance_types:
|
||||
rv.append((t.name, "%s (%sMB memory, %s cpu, %sGB space)" % \
|
||||
(t.name, t.memory_mb, t.vcpus, t.disk_gb)))
|
||||
return rv
|
||||
|
||||
def get_instance_choices(project):
|
||||
choices = [(i.id, i.id) for i in project.get_instances()]
|
||||
if not len(choices):
|
||||
choices = [('', 'none available')]
|
||||
return choices
|
||||
|
||||
def get_key_pair_choices(project):
|
||||
choices = [(k.name, k.name) for k in project.get_key_pairs()]
|
||||
if not len(choices):
|
||||
choices = [('', 'none available')]
|
||||
return choices
|
||||
|
||||
#def get_security_group_choices(project):
|
||||
# choices = [(g.name, g.description) for g in project.get_security_groups()]
|
||||
# if len(choices) == 0:
|
||||
# choices = [('', 'none available')]
|
||||
# return choices
|
||||
|
||||
def get_available_volume_choices(project):
|
||||
choices = [(v.id, '%s %s - %dGB' % (v.id, v.displayName, v.size)) for v in \
|
||||
project.get_volumes() if v.status != "in-use"]
|
||||
if not len(choices):
|
||||
choices = [('', 'none available')]
|
||||
return choices
|
||||
|
||||
def get_protocols():
|
||||
return (
|
||||
('tcp', 'tcp'),
|
||||
('udp', 'udp'),
|
||||
)
|
||||
|
||||
@wrap_nova_error
|
||||
def get_roles(project_roles=True):
|
||||
nova = get_nova_admin_connection()
|
||||
roles = nova.get_roles(project_roles=project_roles)
|
||||
return [(role.role, role.role) for role in roles]
|
||||
|
||||
@wrap_nova_error
|
||||
def get_members(project):
|
||||
nova = get_nova_admin_connection()
|
||||
members = nova.get_project_members(project)
|
||||
return [str(user.memberId) for user in members]
|
||||
|
||||
@wrap_nova_error
|
||||
def set_project_roles(projectname, username, roles):
|
||||
nova = get_nova_admin_connection()
|
||||
# hacky work around to interface correctly with multiple select form
|
||||
_remove_roles(projectname, username)
|
||||
|
||||
for role in roles:
|
||||
nova.add_user_role(username, str(role), projectname)
|
||||
|
||||
def _remove_roles(project, username):
|
||||
nova = get_nova_admin_connection()
|
||||
userroles = nova.get_user_roles(username, project)
|
||||
roles = [str(role.role) for role in userroles]
|
||||
|
||||
for role in roles:
|
||||
if role == "developer":
|
||||
nova.remove_user_role(username, "developer", project)
|
||||
if role == "sysadmin":
|
||||
nova.remove_user_role(username, "sysadmin", project)
|
||||
if role == "netadmin":
|
||||
nova.remove_user_role(username, "netadmin", project)
|
||||
|
||||
|
||||
class ProjectFormBase(forms.Form):
|
||||
def __init__(self, project, *args, **kwargs):
|
||||
self.project = project
|
||||
super(ProjectFormBase, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class LaunchInstanceForm(forms.Form):
|
||||
# nickname = forms.CharField()
|
||||
# description = forms.CharField()
|
||||
|
||||
count = forms.ChoiceField(choices=[(x, x) for x in range(1, 6)])
|
||||
size = forms.ChoiceField()
|
||||
key_name = forms.ChoiceField()
|
||||
#security_group = forms.ChoiceField()
|
||||
user_data = forms.CharField(required=False, widget=forms.widgets.Textarea(attrs={'rows': 4}))
|
||||
|
||||
def __init__(self, project, *args, **kwargs):
|
||||
forms.Form.__init__(self, *args, **kwargs)
|
||||
#self.fields['security_group'].choices = get_security_group_choices(project)
|
||||
self.fields['key_name'].choices = get_key_pair_choices(project)
|
||||
self.fields['size'].choices = get_instance_type_choices()
|
||||
|
||||
|
||||
class UpdateInstanceForm(forms.Form):
|
||||
nickname = forms.CharField(required=False, label="Name")
|
||||
description = forms.CharField(required=False, widget=forms.Textarea, max_length=70)
|
||||
|
||||
def __init__(self, instance, *args, **kwargs):
|
||||
forms.Form.__init__(self, *args, **kwargs)
|
||||
self.fields['nickname'].initial = instance.displayName
|
||||
self.fields['description'].initial = instance.displayDescription
|
||||
|
||||
|
||||
class UpdateImageForm(forms.Form):
|
||||
nickname = forms.CharField(required=False, label="Name")
|
||||
description = forms.CharField(required=False, widget=forms.Textarea, max_length=70)
|
||||
|
||||
def __init__(self, image, *args, **kwargs):
|
||||
forms.Form.__init__(self, *args, **kwargs)
|
||||
self.fields['nickname'].initial = image.displayName
|
||||
self.fields['description'].initial = image.description
|
||||
|
||||
|
||||
class CreateKeyPairForm(ProjectFormBase):
|
||||
name = forms.RegexField(regex=alphanumeric_re)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
|
||||
if self.project.has_key_pair(name):
|
||||
raise forms.ValidationError('A key named %s already exists.' % name)
|
||||
|
||||
return name
|
||||
|
||||
|
||||
class CreateSecurityGroupForm(ProjectFormBase):
|
||||
name = forms.RegexField(regex=alphanumeric_re)
|
||||
description = forms.CharField()
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
|
||||
if self.project.has_security_group(name):
|
||||
raise forms.ValidationError('A security group named %s already exists.' % name)
|
||||
|
||||
return name
|
||||
|
||||
|
||||
class AuthorizeSecurityGroupRuleForm(forms.Form):
|
||||
protocol = forms.ChoiceField(choices=get_protocols())
|
||||
from_port = forms.IntegerField(min_value=1, max_value=65535)
|
||||
to_port = forms.IntegerField(min_value=1, max_value=65535)
|
||||
|
||||
|
||||
class CreateVolumeForm(forms.Form):
|
||||
size = forms.IntegerField(label='Size (in GB)', min_value=1, max_value=MAX_VOLUME_SIZE)
|
||||
nickname = forms.CharField()
|
||||
description = forms.CharField()
|
||||
|
||||
|
||||
class AttachVolumeForm(ProjectFormBase):
|
||||
volume = forms.ChoiceField()
|
||||
instance = forms.ChoiceField()
|
||||
device = forms.CharField(initial='/dev/vdb')
|
||||
|
||||
def __init__(self, project, *args, **kwargs):
|
||||
super(AttachVolumeForm, self).__init__(project, *args, **kwargs)
|
||||
self.fields['volume'].choices = get_available_volume_choices(project)
|
||||
self.fields['instance'].choices = get_instance_choices(project)
|
||||
|
||||
|
||||
class ProjectForm(forms.Form):
|
||||
projectname = forms.CharField(label="Project Name", max_length=20)
|
||||
description = forms.CharField(label="Description",
|
||||
widget=forms.widgets.Textarea())
|
||||
manager = forms.ModelChoiceField(queryset=auth_models.User.objects.all(),
|
||||
label="Project Manager")
|
||||
|
||||
|
||||
class GlobalRolesForm(forms.Form):
|
||||
role = forms.MultipleChoiceField(label='Roles', required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GlobalRolesForm, self).__init__(*args, **kwargs)
|
||||
self.fields['role'].choices = get_roles(project_roles=False)
|
||||
|
||||
|
||||
class ProjectUserForm(forms.Form):
|
||||
role = forms.MultipleChoiceField(label='Roles', required=False)
|
||||
|
||||
def __init__(self, project, user, *args, **kwargs):
|
||||
super(ProjectUserForm, self).__init__(*args, **kwargs)
|
||||
self.project = project
|
||||
self.user = user
|
||||
self.fields['role'].choices = get_roles()
|
||||
|
||||
def save(self):
|
||||
set_project_roles(self.project.projectname,
|
||||
self.user.username,
|
||||
self.cleaned_data['role'])
|
||||
|
||||
|
||||
class AddProjectUserForm(forms.Form):
|
||||
username = forms.ModelChoiceField(queryset='',
|
||||
label='Username',
|
||||
empty_label='Select a Username')
|
||||
role = forms.MultipleChoiceField(label='Roles')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
project = kwargs.pop('project')
|
||||
super(AddProjectUserForm, self).__init__(*args, **kwargs)
|
||||
members = get_members(project)
|
||||
|
||||
self.fields['username'].queryset = \
|
||||
auth_models.User.objects.exclude(username__in=members)
|
||||
self.fields['role'].choices = get_roles()
|
||||
|
||||
|
||||
class SendCredentialsForm(forms.Form):
|
||||
users = forms.MultipleChoiceField(label='Users', required=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
query_list = kwargs.pop('query_list')
|
||||
super(SendCredentialsForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['users'].choices = [(choices, choices) for choices in query_list]
|
||||
|
@ -0,0 +1,37 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Management commands for synchronizing the Django auth database and Nova
|
||||
users database.
|
||||
"""
|
||||
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from django.contrib.auth.models import User
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = 'Creates nova users for all users in the django auth database.'
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
nova = get_nova_admin_connection()
|
||||
users = User.objects.all()
|
||||
for user in users:
|
||||
if not nova.has_user(user.username):
|
||||
self.stdout.write('creating user %s... ' % user.username)
|
||||
nova.create_user(user.username)
|
||||
self.stdout.write('ok\n')
|
340
django-nova/src/django_nova/manager.py
Normal file
340
django-nova/src/django_nova/manager.py
Normal file
@ -0,0 +1,340 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Simple API for interacting with Nova projects.
|
||||
"""
|
||||
|
||||
import boto
|
||||
import boto.ec2.volume
|
||||
import boto.exception
|
||||
import boto.s3
|
||||
from django.conf import settings
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
from django_nova.exceptions import wrap_nova_error
|
||||
|
||||
|
||||
class ProjectManager(object):
|
||||
def __init__(self, username, project, region):
|
||||
self.username = username
|
||||
self.projectname = project.projectname
|
||||
self.projectManagerId = project.projectManagerId
|
||||
self.region = region
|
||||
|
||||
def get_nova_connection(self):
|
||||
"""
|
||||
Returns a boto connection for a user's project.
|
||||
"""
|
||||
nova = get_nova_admin_connection()
|
||||
return nova.connection_for(self.username,
|
||||
self.projectname,
|
||||
clc_url=self.region['endpoint'],
|
||||
region=self.region['name'])
|
||||
|
||||
def get_zip(self):
|
||||
"""
|
||||
Returns a buffer of a zip file containing signed credentials
|
||||
for the project's Nova user.
|
||||
"""
|
||||
nova = get_nova_admin_connection()
|
||||
return nova.get_zip(self.username, self.projectname)
|
||||
|
||||
def get_images(self, image_ids=None):
|
||||
conn = self.get_nova_connection()
|
||||
images = conn.get_all_images(image_ids=image_ids)
|
||||
sorted_images = [i for i in images if i.ownerId == self.username] + \
|
||||
[i for i in images if i.ownerId != self.username]
|
||||
|
||||
return [i for i in sorted_images if i.type == 'machine' and i.location.split('/')[0] != 'nova']
|
||||
|
||||
def get_image(self, image_id):
|
||||
try:
|
||||
return self.get_images(image_ids=[image_id,])[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@wrap_nova_error
|
||||
def deregister_image(self, image_id):
|
||||
"""
|
||||
Removes the image's listing but leaves the image
|
||||
and manifest in the object store in tact.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.deregister_image(image_id)
|
||||
|
||||
@wrap_nova_error
|
||||
def update_image(self, image_id, display_name=None, description=None):
|
||||
conn = self.get_nova_connection()
|
||||
params = {
|
||||
'ImageId': image_id,
|
||||
'DisplayName': display_name,
|
||||
'Description': description
|
||||
}
|
||||
return conn.get_object('UpdateImage', params, boto.ec2.image.Image)
|
||||
|
||||
@wrap_nova_error
|
||||
def modify_image_attribute(self, image_id, attribute=None, operation=None,
|
||||
groups='all'):
|
||||
conn = self.get_nova_connection()
|
||||
return conn.modify_image_attribute(image_id,
|
||||
attribute='launchPermission',
|
||||
operation='remove',
|
||||
groups='all',)
|
||||
|
||||
|
||||
@wrap_nova_error
|
||||
def run_instances(self, image_id, **kwargs):
|
||||
"""
|
||||
Runs instances of the specified image id.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.run_instances(image_id, **kwargs)
|
||||
|
||||
def get_instance_count(self):
|
||||
"""
|
||||
Returns the number of active instances in this project or None if unknown.
|
||||
"""
|
||||
try:
|
||||
return len(self.get_instances())
|
||||
except:
|
||||
return None
|
||||
|
||||
@wrap_nova_error
|
||||
def get_instances(self):
|
||||
"""
|
||||
Returns all instances in this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
reservations = conn.get_all_instances()
|
||||
instances = []
|
||||
for reservation in reservations:
|
||||
for instance in reservation.instances:
|
||||
instances.append(instance)
|
||||
return instances
|
||||
|
||||
@wrap_nova_error
|
||||
def get_instance(self, instance_id):
|
||||
"""
|
||||
Returns detail about the specified instance.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
# TODO: Refactor this once nova's describe_instances filters by instance_id.
|
||||
reservations = conn.get_all_instances()
|
||||
for reservation in reservations:
|
||||
for instance in reservation.instances:
|
||||
if instance.id == instance_id:
|
||||
return instance
|
||||
return None
|
||||
|
||||
@wrap_nova_error
|
||||
def update_instance(self, instance_id, updates):
|
||||
conn = self.get_nova_connection()
|
||||
params = {'InstanceId': instance_id, 'DisplayName': updates['nickname'],
|
||||
'DisplayDescription': updates['description']}
|
||||
return conn.get_object('UpdateInstance', params,
|
||||
boto.ec2.instance.Instance)
|
||||
|
||||
def get_instance_graph(self, region, instance_id, graph_name):
|
||||
# TODO(devcamcar): Need better support for multiple regions.
|
||||
# Need a way to get object store by region.
|
||||
s3 = boto.s3.connection.S3Connection (
|
||||
aws_access_key_id=settings.NOVA_ACCESS_KEY,
|
||||
aws_secret_access_key=settings.NOVA_SECRET_KEY,
|
||||
is_secure=False,
|
||||
calling_format=boto.s3.connection.OrdinaryCallingFormat(),
|
||||
port=3333,
|
||||
host=settings.NOVA_CLC_IP
|
||||
)
|
||||
key = '_%s.monitor' % instance_id
|
||||
|
||||
try:
|
||||
bucket = s3.get_bucket(key, validate=False)
|
||||
except boto.exception.S3ResponseError, e:
|
||||
if e.code == "NoSuchBucket":
|
||||
return None
|
||||
else:
|
||||
raise e
|
||||
|
||||
key = bucket.get_key(graph_name)
|
||||
|
||||
return key.read()
|
||||
|
||||
@wrap_nova_error
|
||||
def terminate_instance(self, instance_id):
|
||||
""" Terminates the specified instance within this project. """
|
||||
conn = self.get_nova_connection()
|
||||
conn.terminate_instances([instance_id])
|
||||
|
||||
@wrap_nova_error
|
||||
def get_security_groups(self):
|
||||
"""
|
||||
Returns all security groups associated with this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
groups = []
|
||||
|
||||
for g in conn.get_all_security_groups():
|
||||
# Do not show vpn group.
|
||||
#if g.name != 'vpn-secgroup':
|
||||
groups.append(g)
|
||||
|
||||
return groups
|
||||
|
||||
@wrap_nova_error
|
||||
def get_security_group(self, name):
|
||||
"""
|
||||
Returns the specified security group for this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
|
||||
try:
|
||||
return conn.get_all_security_groups(groupnames=name.encode('ASCII'))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@wrap_nova_error
|
||||
def has_security_group(self, name):
|
||||
"""
|
||||
Indicates whether a security group with the specified name exists in this project.
|
||||
"""
|
||||
return self.get_security_group(name) is not None
|
||||
|
||||
@wrap_nova_error
|
||||
def create_security_group(self, name, description):
|
||||
"""
|
||||
Creates a new security group in this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.create_security_group(name, description)
|
||||
|
||||
@wrap_nova_error
|
||||
def delete_security_group(self, name):
|
||||
"""
|
||||
Deletes a security group from the project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.delete_security_group(name = name)
|
||||
|
||||
@wrap_nova_error
|
||||
def authorize_security_group(self, group_name, ip_protocol, from_port, to_port):
|
||||
"""
|
||||
Authorizes a rule for the specified security group.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.authorize_security_group (
|
||||
group_name = group_name,
|
||||
ip_protocol = ip_protocol,
|
||||
from_port = from_port,
|
||||
to_port = to_port,
|
||||
cidr_ip = '0.0.0.0/0'
|
||||
)
|
||||
|
||||
@wrap_nova_error
|
||||
def revoke_security_group(self, group_name, ip_protocol, from_port, to_port):
|
||||
"""
|
||||
Revokes a rule for the specified security group.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.revoke_security_group (
|
||||
group_name = group_name,
|
||||
ip_protocol = ip_protocol,
|
||||
from_port = from_port,
|
||||
to_port = to_port,
|
||||
cidr_ip = '0.0.0.0/0'
|
||||
)
|
||||
|
||||
@wrap_nova_error
|
||||
def get_key_pairs(self):
|
||||
"""
|
||||
Returns all key pairs associated with this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
keys = []
|
||||
|
||||
for k in conn.get_all_key_pairs():
|
||||
# Do not show vpn key.
|
||||
if k.name != 'vpn-key':
|
||||
keys.append(k)
|
||||
|
||||
return keys
|
||||
|
||||
@wrap_nova_error
|
||||
def get_key_pair(self, name):
|
||||
"""
|
||||
Returns the specified security group for this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
|
||||
try:
|
||||
return conn.get_all_key_pairs(keynames=name.encode('ASCII'))[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@wrap_nova_error
|
||||
def has_key_pair(self, name):
|
||||
"""
|
||||
Indicates whether a key pair with the specified name exists in this project.
|
||||
"""
|
||||
return self.get_key_pair(name) != None
|
||||
|
||||
@wrap_nova_error
|
||||
def create_key_pair(self, name):
|
||||
"""
|
||||
Creates a new key pair for this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.create_key_pair(name)
|
||||
|
||||
@wrap_nova_error
|
||||
def delete_key_pair(self, name):
|
||||
"""
|
||||
Deletes a new key pair from this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
conn.delete_key_pair(name)
|
||||
|
||||
@wrap_nova_error
|
||||
def get_volumes(self):
|
||||
"""
|
||||
Returns all volumes in this project.
|
||||
"""
|
||||
conn = self.get_nova_connection()
|
||||
return conn.get_all_volumes()
|
||||
|
||||
@wrap_nova_error
|
||||
def create_volume(self, size, display_name=None, display_description=None,
|
||||
snapshot=None):
|
||||
conn = self.get_nova_connection()
|
||||
params = {'Size': size, 'DisplayName': display_name,
|
||||
'DisplayDescription': display_description}
|
||||
return conn.get_object('CreateVolume', params, boto.ec2.volume.Volume)
|
||||
|
||||
@wrap_nova_error
|
||||
def delete_volume(self, volume_id):
|
||||
conn = self.get_nova_connection()
|
||||
return conn.delete_volume(volume_id)
|
||||
|
||||
@wrap_nova_error
|
||||
def attach_volume(self, volume_id, instance_id, device):
|
||||
conn = self.get_nova_connection()
|
||||
return conn.attach_volume(volume_id, instance_id, device)
|
||||
|
||||
@wrap_nova_error
|
||||
def detach_volume(self, volume_id):
|
||||
conn = self.get_nova_connection()
|
||||
return conn.detach_volume(volume_id)
|
||||
|
121
django-nova/src/django_nova/models.py
Normal file
121
django-nova/src/django_nova/models.py
Normal file
@ -0,0 +1,121 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Database models for authorization credentials and synchronizing Nova users.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import random
|
||||
import re
|
||||
import sha
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.contrib.sites import models as site_models
|
||||
from django.core import mail
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.template.loader import render_to_string
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
|
||||
|
||||
SHA1_RE=re.compile('^[a-f0-9]{40}$')
|
||||
|
||||
|
||||
class CredentialsAuthorization(models.Model):
|
||||
username = models.CharField(max_length=128)
|
||||
project = models.CharField(max_length=128)
|
||||
auth_token = models.CharField(max_length=40)
|
||||
auth_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return '%s/%s:%s' % (self.username, self.project, self.auth_token)
|
||||
|
||||
@classmethod
|
||||
def get_by_token(cls, token):
|
||||
if SHA1_RE.search(token):
|
||||
try:
|
||||
credentials = cls.objects.get(auth_token=token)
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
if not credentials.auth_token_expired():
|
||||
return credentials
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def authorize(cls, username, project):
|
||||
return cls.objects.create(username=username,
|
||||
project=project,
|
||||
auth_token=cls.create_auth_token(username))
|
||||
|
||||
@staticmethod
|
||||
def create_auth_token(username):
|
||||
salt = sha.new(str(random.random())).hexdigest()[:5]
|
||||
return sha.new(salt+username).hexdigest()
|
||||
|
||||
def auth_token_expired(self):
|
||||
expiration_date = datetime.timedelta(days=int(settings.CREDENTIAL_AUTHORIZATION_DAYS))
|
||||
|
||||
return self.auth_date + expiration_date <= datetime.datetime.now()
|
||||
|
||||
def get_download_url(self):
|
||||
return settings.CREDENTIAL_DOWNLOAD_URL + self.auth_token
|
||||
|
||||
def get_zip(self):
|
||||
nova = get_nova_admin_connection()
|
||||
self.delete()
|
||||
return nova.get_zip(self.username, self.project)
|
||||
|
||||
|
||||
def credentials_post_save(sender, instance, created, *args, **kwargs):
|
||||
"""
|
||||
Creates a Nova User when a new Django User is created.
|
||||
"""
|
||||
if created:
|
||||
site = site_models.Site.objects.get_current()
|
||||
user = auth_models.User.objects.get(username=instance.username)
|
||||
context = {
|
||||
'user': user,
|
||||
'download_url': instance.get_download_url(),
|
||||
'dashboard_url': 'http://%s/' % site.domain
|
||||
}
|
||||
subject = render_to_string('credentials/credentials_email_subject.txt')
|
||||
body = render_to_string('credentials/credentials_email.txt', context)
|
||||
|
||||
message = mail.EmailMessage(subject=subject, body=body, to=[user.email])
|
||||
message.send(fail_silently=False)
|
||||
post_save.connect(credentials_post_save,
|
||||
CredentialsAuthorization,
|
||||
dispatch_uid='django_nova.CredentialsAuthorization.post_save')
|
||||
|
||||
|
||||
def user_post_save(sender, instance, created, *args, **kwargs):
|
||||
"""
|
||||
Creates a Nova User when a new Django User is created.
|
||||
"""
|
||||
|
||||
# NOTE(devcamcar): If running unit tests, don't use a real endpoint.
|
||||
if settings.NOVA_DEFAULT_ENDPOINT == 'none':
|
||||
return
|
||||
|
||||
if created:
|
||||
nova = get_nova_admin_connection()
|
||||
if not nova.has_user(instance.username):
|
||||
nova.create_user(instance.username)
|
||||
post_save.connect(user_post_save,
|
||||
auth_models.User,
|
||||
dispatch_uid='django_nova.User.post_save')
|
131
django-nova/src/django_nova/shortcuts.py
Normal file
131
django-nova/src/django_nova/shortcuts.py
Normal file
@ -0,0 +1,131 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Helper methods for commonly used operations.
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django_nova import manager
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
from django_nova.exceptions import wrap_nova_error
|
||||
|
||||
|
||||
@wrap_nova_error
|
||||
def get_project_or_404(request, project_id):
|
||||
"""
|
||||
Returns a project or 404s if it doesn't exist.
|
||||
"""
|
||||
|
||||
# Ensure that a connection is never attempted for a user that is unauthenticated.
|
||||
if not request.user.is_authenticated:
|
||||
raise PermissionDenied('User not authenticated')
|
||||
|
||||
nova = get_nova_admin_connection()
|
||||
project = nova.get_project(project_id)
|
||||
region = get_current_region(request)
|
||||
|
||||
if not project:
|
||||
raise Http404('Project %s does not exist.' % project_id)
|
||||
|
||||
return manager.ProjectManager(request.user, project, region)
|
||||
|
||||
|
||||
@wrap_nova_error
|
||||
def get_projects(user):
|
||||
"""
|
||||
Returns a list of projects for a user.
|
||||
"""
|
||||
#key = 'projects.%s' % user
|
||||
#projects = cache.get(key)
|
||||
|
||||
#if not projects:
|
||||
# nova = get_nova_admin_connection()
|
||||
# projects = nova.get_projects(user=user)
|
||||
# cache.set(key, projects, 30)
|
||||
|
||||
#return projects
|
||||
nova = get_nova_admin_connection()
|
||||
return nova.get_projects(user=user)
|
||||
|
||||
|
||||
@wrap_nova_error
|
||||
def get_all_regions():
|
||||
"""
|
||||
Returns a list of all regions.
|
||||
"""
|
||||
regions = cache.get('regions')
|
||||
|
||||
if not regions:
|
||||
nova = get_nova_admin_connection()
|
||||
conn = nova.connection_for(settings.NOVA_ADMIN_USER, settings.NOVA_PROJECT)
|
||||
results = conn.get_all_regions()
|
||||
regions = [{'name': r.name, 'endpoint': r.endpoint} for r in results]
|
||||
cache.set('regions', regions, 60 * 60 * 24)
|
||||
|
||||
return regions
|
||||
|
||||
|
||||
def get_region(region_name):
|
||||
regions = get_all_regions()
|
||||
try:
|
||||
return [r for r in regions if r['name'] == region_name][0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
|
||||
def get_current_region(request):
|
||||
"""
|
||||
Returns the currently selected region for a user.
|
||||
"""
|
||||
region_name = request.session.get('region', settings.NOVA_DEFAULT_REGION)
|
||||
return get_region(region_name)
|
||||
|
||||
|
||||
def set_current_region(request, region_name):
|
||||
"""
|
||||
Sets the current region selection for a user.
|
||||
"""
|
||||
request.session['region'] = region_name
|
||||
|
||||
|
||||
@wrap_nova_error
|
||||
def get_user_image_permissions(username, project_name):
|
||||
"""
|
||||
Returns true if user is a sysadmin and can modify image attributes.
|
||||
"""
|
||||
nova = get_nova_admin_connection()
|
||||
user_has_modify_permissions = False
|
||||
|
||||
# checks global roles, if user is a sysadmin they can modify image attribtues.
|
||||
if not user_has_modify_permissions:
|
||||
for role in nova.get_user_roles(username):
|
||||
if role.role == "sysadmin":
|
||||
user_has_modify_permissions = True
|
||||
|
||||
# checks project roles, if user is a sysadmin they can modify image attribtues.
|
||||
if not user_has_modify_permissions:
|
||||
for role in nova.get_user_roles(username, project_name):
|
||||
if role.role == "sysadmin":
|
||||
user_has_modify_permissions = True
|
||||
|
||||
return user_has_modify_permissions
|
||||
|
@ -0,0 +1,45 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% load admin_modify adminmedia %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
|
||||
|
||||
{% block coltype %}colMS{% endblock %}
|
||||
|
||||
{% block bodyclass %} change-form{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
<a href="../../projects">Projects</a> ›
|
||||
Add Project
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% endblock %}
|
||||
<form action="." method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
|
||||
</fieldset>
|
||||
<input type="submit" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,69 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% load admin_modify adminmedia %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media }}
|
||||
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
django.jQuery(function(){
|
||||
django.jQuery.each(django.jQuery(".edit_user_roles select[multiple]"), function () {
|
||||
// "Locations" can be any label you want
|
||||
SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
|
||||
|
||||
{% block coltype %}colMS{% endblock %}
|
||||
|
||||
{% block bodyclass %} change-form{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
<a href="{% url admin_projects %}">Projects</a> ›
|
||||
<a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> ›
|
||||
User
|
||||
{{form.ProjectUserForm}}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% endblock %}
|
||||
<form class="edit_user_roles" action="." method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
<input type="hidden" name="username" value="{{user.id}}" id="username" />
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
|
||||
</fieldset>
|
||||
<div class="submit-row">
|
||||
<p class="deletelink-box">
|
||||
<a href="{% url admin_project_delete_user project.projectname user.username %}" class="deletelink">Delete</a>
|
||||
</p>
|
||||
<input type="submit" value="Save" class="default" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,16 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{block.super}}
|
||||
<link rel="stylesheet" type="text/css" href="{{settings.MEDIA_URL}}/stylesheets/extra_admin.css" />
|
||||
{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">Home</a> › Projects</div>{% endblock %}
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
<div class="module filtered" id="changelist">
|
||||
<div id="toolbartable">
|
||||
{% block innercontent %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,3 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load admin_extras %}
|
||||
{% block result_list %}{% project_result_list cl %}{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{block.super}}
|
||||
<link rel="stylesheet" type="text/css" href="{{settings.MEDIA_URL}}/stylesheets/extra_admin.css" />
|
||||
{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">Home</a> › <a href="/admin/projects">Projects</a> › <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> › Delete</div>{% endblock %}
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
<div class="module filtered" id="changelist">
|
||||
<div id="toolbartable">
|
||||
<h1>Delete Project</h1>
|
||||
<p>Do you really want to delete this project?</p>
|
||||
<ul>
|
||||
<li><a href="{% url admin_project project.projectname %}">{{project.projectname}}</a></li>
|
||||
</ul>
|
||||
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
<p><input type="submit" value="Delete"></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{block.super}}
|
||||
<link rel="stylesheet" type="text/css" href="{{settings.MEDIA_URL}}/stylesheets/extra_admin.css" />
|
||||
{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">Home</a> › <a href="/admin/projects">Projects</a> › <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> › Delete</div>{% endblock %}
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
<div class="module filtered" id="changelist">
|
||||
<div id="toolbartable">
|
||||
<h1>Remove User From Project</h1>
|
||||
<p>Do you really want to remove this user from project?</p>
|
||||
<ul>
|
||||
<li><a href="{% url project_user project.projectname user.username %}">{{user.username}}</a> from <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a></li>
|
||||
</ul>
|
||||
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
<p><input type="submit" value="Delete"></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,95 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% load admin_modify adminmedia %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
|
||||
|
||||
{% block coltype %}colMS{% endblock %}
|
||||
|
||||
{% block bodyclass %} change-form{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
<a href="{% url admin_projects %}">Projects</a> ›
|
||||
<a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> ›
|
||||
Edit Project
|
||||
{{form.ProjectEditForm}}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% endblock %}
|
||||
<form action="#" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<div class="form-row">
|
||||
<label for="project_name">Project Name</label>
|
||||
<span id="project_name" style="display: block; padding-top: 5px; padding-left: 5px; float: left;"><em>{{projectname}}</em></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="project_description">Description</label>
|
||||
<span id="project_description" style="display: block; padding-top: 5px; padding-left: 5px; float: left;"><em>{{description}}</em></span>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="project_manager">Project Manager</label>
|
||||
<span id="project_manager" style="display: block; padding-top: 5px; padding-left: 5px; float: left;"><em>{{manager}}</em></span>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="submit-row">
|
||||
<p class="deletelink-box">
|
||||
<a href="{% url delete_project project.projectname %}" class="deletelink">Delete Project</a>
|
||||
</p>
|
||||
{# <input type="submit" value="Save" class="default" /> #}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
<table cellspacing="0" style="margin-top: 20px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Project Roles</th>
|
||||
<th>Global Roles</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr class="{% cycle 'row1' 'row2' %}">
|
||||
<td>
|
||||
<a href="{%url project_user project.projectname user.memberId %}">{{user.memberId}} {% if user.memberId == project.projectManagerId %}(<em>project manager</em>){%endif %}</a>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{{user.project_roles}}
|
||||
</td>
|
||||
<td>
|
||||
{{user.global_roles}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<ul class="object-tools">
|
||||
<li>
|
||||
<a class="addlink" href="{% url add_project_user project.projectname %}">Add User</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,71 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% load admin_modify adminmedia %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media }}
|
||||
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
django.jQuery(function(){
|
||||
django.jQuery.each(django.jQuery("#global_users select"), function () {
|
||||
// "Locations" can be any label you want
|
||||
SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
|
||||
|
||||
{% block coltype %}colMS{% endblock %}
|
||||
|
||||
{% block bodyclass %} change-form{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
<a href="{% url admin_users_list %}">Global Roles</a> ›
|
||||
{{user.username}}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% endblock %}
|
||||
<form action="." method="post" enctype="multipart/form-data" id="global_users">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
<div class="form-row">
|
||||
<label for="id_username">Username</label>
|
||||
<span>{{user.username}}</span>
|
||||
</div>
|
||||
<input type="hidden" name="username" value="{{user.id}}" id="username" />
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
|
||||
</fieldset>
|
||||
<div class="submit-row">
|
||||
<p class="deletelink-box">
|
||||
{# <a href="{% url admin_project_delete_user project.projectname user.username %}" class="deletelink">Delete</a> #}
|
||||
</p>
|
||||
<input type="submit" value="Save" class="default" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,42 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
{% block innercontent %}
|
||||
<ul class="object-tools">
|
||||
<li>
|
||||
<a class="addlink" href="{% url add_project %}">Add Project</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table cellspacing="0" style="margin-top: 20px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Project Manager</th>
|
||||
<th>Send Credentials</th>
|
||||
<th>Start VPN</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for project in projects %}
|
||||
<tr class="{% cycle 'row1' 'row2' %}">
|
||||
<td>
|
||||
<a href="{%url admin_project project.projectname %}">{{project.projectname}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{project.description}}
|
||||
</td>
|
||||
<td>
|
||||
{{project.projectManagerId}}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url admin_project_sendcredentials project.projectname %}">Send Credentials</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url admin_project_start_vpn project.projectname %}">Start VPN</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
@ -0,0 +1,76 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% load admin_modify adminmedia %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media }}
|
||||
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
django.jQuery(function(){
|
||||
django.jQuery.each(django.jQuery(".edit_user_roles select[multiple]"), function () {
|
||||
// "Locations" can be any label you want
|
||||
SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
|
||||
|
||||
{% block coltype %}colMS{% endblock %}
|
||||
|
||||
{% block bodyclass %} change-form{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
<a href="{% url admin_projects %}">Projects</a> ›
|
||||
<a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> ›
|
||||
User
|
||||
{{form.ProjectUserForm}}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% endblock %}
|
||||
<form class="edit_user_roles" action="." method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
<div class="form-row">
|
||||
<label for="id_username">Username</label>
|
||||
<span>{{user.username}}</span>
|
||||
</div>
|
||||
<input type="hidden" name="username" value="{{user.id}}" id="username" />
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
|
||||
</fieldset>
|
||||
<div class="submit-row">
|
||||
{% if project.projectManagerId != user.username %}
|
||||
<p class="deletelink-box">
|
||||
<a href="{% url admin_project_delete_user project.projectname user.username %}" class="deletelink">Remove User From Project</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<input type="submit" value="Save" class="default" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,87 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n admin_modify adminmedia %}
|
||||
|
||||
{% block title %}Send project credentials{{ block.super }}{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{{ media }}
|
||||
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
django.jQuery(function(){
|
||||
django.jQuery.each(django.jQuery("#send_credentials select"), function () {
|
||||
// "Locations" can be any label you want
|
||||
SelectFilter.init(this.id, "Users", 0, "/media/admin/");
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
.errorlist, .successlist {background:#fcc;border:1px solid #c66;color:#600;list-style:none; padding: 10px 5px; margin: 25px 0 25px 0; float: left; width: 100%;}
|
||||
.successlist {background: #CBFBD7; color: #1E5024; border-color: #6FBA5C;}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
<a href="{% url admin_projects %}">Projects</a> ›
|
||||
<a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> ›
|
||||
Send Credentials
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
|
||||
|
||||
{% if not success %}
|
||||
<h1>Send Credentials</h1>
|
||||
<h3>Select which users you would like to send credentials to from the '{{ project.projectname }}' project.</h3>
|
||||
{% else %}
|
||||
<h1>Credentials sent successfully</h1>
|
||||
{% endif %}
|
||||
|
||||
<div class="status">
|
||||
{% if error %}
|
||||
<span class="errorlist">{{ error }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if success %}
|
||||
<span class="successlist">{{ success }}</span>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if not success %}
|
||||
<form id="send_credentials" action="{% url admin_project_sendcredentials project.projectname %}" method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned">
|
||||
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
|
||||
</fieldset>
|
||||
<div class="submit-row">
|
||||
<input style="margin-top:20px; margin-left:10px;" type="submit" value="Send Credentials" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,39 @@
|
||||
{% extends "admin/django_nova/project/base_projects.html" %}
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="/admin">Home</a> ›
|
||||
Global Roles
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block innercontent %}
|
||||
<h1>Select a User</h1>
|
||||
|
||||
<table cellspacing="0" style="margin-top: 20px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Global Roles</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr class="{% cycle 'row1' 'row2' %}">
|
||||
<td>
|
||||
{{user.username}}
|
||||
</td>
|
||||
<td>
|
||||
(temporarily hidden)
|
||||
{#user.roles#}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{%url admin_user_roles user.username %}">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
@ -0,0 +1,41 @@
|
||||
{% for message in messages %}
|
||||
<div class="message ui-widget">
|
||||
{% if message.tags == "info" %}
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<span class="close ui-icon ui-icon-circle-close"></span>
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if message.tags == "warning" %}
|
||||
<div class="ui-state-highlight ui-corner-all" >
|
||||
<span class="close ui-icon ui-icon-circle-close"></span>
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-alert"></span>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if message.tags == "success" %}
|
||||
<div class="ui-state-highlight ui-corner-all success" >
|
||||
<span class="close ui-icon ui-icon-circle-close"></span>
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-check"></span>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if message.tags == "error" %}
|
||||
<div class="ui-state-error ui-corner-all" >
|
||||
<span class="close ui-icon ui-icon-circle-close"></span>
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-alert"></span>
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
85
django-nova/src/django_nova/templates/django_nova/base.html
Normal file
85
django-nova/src/django_nova/templates/django_nova/base.html
Normal file
@ -0,0 +1,85 @@
|
||||
{% extends "base-sidebar.html" %}
|
||||
{% load region_tags %}
|
||||
{% load project_tags %}
|
||||
|
||||
{% block headerjs %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block region %}
|
||||
<div id="region_selector">
|
||||
{% load_regions %}
|
||||
<span id="project_name"><strong>Project:</strong> {{ project.projectname }}</span>
|
||||
|
||||
<form id="frm_region" method="post" action="{% url region_change %}">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<input name="redirect_url" type="hidden" value="{{ request.get_full_path }}" />
|
||||
<noscript>
|
||||
<input id="btn_region_change" type="submit" value="Change" />
|
||||
</noscript>
|
||||
|
||||
<div id="region_form">
|
||||
<label for="sel_region">Region: </label>
|
||||
<select id="sel_region" name="region">
|
||||
{% for region in regions %}
|
||||
<option{% if region.name == current_region.name %} selected{% endif %}>
|
||||
{{ region.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block nav_projects %}
|
||||
{% load_projects %}
|
||||
<li>
|
||||
<h3 class="active"><a href="/">Projects</a></h3>
|
||||
<div id="projects">
|
||||
{% for p in projects %}
|
||||
<div id="{{ p.projectname }}" class="project{% if p.projectname == project.projectname %} active{% endif %}">
|
||||
<h4>
|
||||
<a class="project_link" href="/project/{{ p.projectname }}">{{ p.projectname }}</a>
|
||||
{% if p.projectManagerId == user.username %}
|
||||
<a id="manage_project_{{p.projectname}}"
|
||||
class="manage_link"
|
||||
href="{% url nova_project_manage p.projectname %}"
|
||||
title="Manage User Roles">Manage Project</a>
|
||||
{% endif %}
|
||||
</h4>
|
||||
{% if project.projectname == p.projectname %}
|
||||
<ul>
|
||||
<li {% if p.projectname == project.projectname and sidebar_selected == "instances" %}class="active"{% endif %}>
|
||||
<a id="lnk_instances_{{p.projectname}}" href="{% url nova_instances p.projectname %}">Instances</a>
|
||||
</li>
|
||||
<li {% if p.projectname == project.projectname and sidebar_selected == "images" %}class="active"{% endif %}>
|
||||
<a id="lnk_images_{{p.projectname}}" href="{% url nova_images p.projectname %}">Images</a>
|
||||
</li>
|
||||
<li {% if p.projectname == project.projectname and sidebar_selected == "keys" %}class="active"{% endif %}>
|
||||
<a id="lnk_keypairs_{{p.projectname}}" href="{% url nova_keypairs p.projectname %}">Keys</a>
|
||||
</li>
|
||||
<li {% if p.projectname == project.projectname and sidebar_selected == "volumes" %}class="active"{% endif %}>
|
||||
<a id="lnk_volumes_{{p.projectname}}" href="{% url nova_volumes p.projectname %}">Volumes</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('#sel_region').change(function() {
|
||||
$('#frm_region').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,17 @@
|
||||
{% load django_nova_tags %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Expired Token</title>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h1>The link you clicked has expired.</h1>
|
||||
<p style="width:460px;">This credentials download link you have reached
|
||||
is either invalid or has expired. Each link is only good for one use. If
|
||||
you need to download your credentials again, please contact the
|
||||
{% site_branding %} support team.</p>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,7 @@
|
||||
{% for field in form %}
|
||||
<div class="{% cycle 'odd' 'even'%}">
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
</div>
|
||||
{% endfor %}
|
@ -0,0 +1,112 @@
|
||||
<h3 class="image_list_heading"> {{ heading }} </h3>
|
||||
{% if images %}
|
||||
<table id="image_launch">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Description</th>
|
||||
<th colspan="2">Owner</th>
|
||||
</tr>
|
||||
{% for image in images %}
|
||||
{% if image.id == ami.id %}
|
||||
<td class="detail_wrapper" colspan="4">
|
||||
<div id="{{ ami.id }}" class="image_detail">
|
||||
<div class="column">
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Owner: </span>
|
||||
<span class="data">{{ ami.ownerId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Description: </span>
|
||||
<span class="data">{{ ami.description }}</span>
|
||||
</div>
|
||||
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Location: </span>
|
||||
<span class="data">{{ ami.location }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="image_detail_item">
|
||||
<span class="label">ID: </span>
|
||||
<span class="data">{{ ami.id }}</span>
|
||||
</div>
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Name: </span>
|
||||
<span class="data">{% if ami.displayName %}{{ ami.displayName }}{%else%}{{ ami.id }}{% endif %}</span>
|
||||
</div>
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Type: </span>
|
||||
<span class="data">{{ ami.type }}</span>
|
||||
</div>
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Architecture: </span>
|
||||
<span class="data">{{ ami.architecture }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="last" class="column">
|
||||
{% if ami.is_public %}
|
||||
<div id="public" class="privacy">Public Image</div>
|
||||
{% else %}
|
||||
<div id="private" class="privacy">Private Image</div>
|
||||
{% endif %}
|
||||
|
||||
<a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}" title="Click to launch image">Launch</a>
|
||||
{% if can_modify or user.username == ami.ownerId %}
|
||||
<a id="edit_image_link" href="{% url nova_images_update project.projectname ami.id %}">Edit Image</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if can_modify or user.username == ami.ownerId %}
|
||||
<span class="image_privacy">
|
||||
<form id="privacy_{{ ami.id }}" action="{% url nova_images_privacy project.projectname ami.id %}" method="post" accept-charset="utf-8">
|
||||
{% csrf_token %}
|
||||
{% if ami.is_public %}
|
||||
<input class="private" type="submit" value="Make Private" />
|
||||
{% else %}
|
||||
<input class="public" type="submit" value="Make Public" />
|
||||
{% endif %}
|
||||
</form>
|
||||
</span>
|
||||
|
||||
<span class="delete">
|
||||
<form id="delete_{{ ami.id }}" action="{% url nova_images_remove project.projectname ami.id %}" method="post" accept-charset="utf-8">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Remove Image" />
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
{% else %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td class="image_id">
|
||||
<a href="{% url nova_images_detail project.projectname image.id %}">{% if image.displayName %}{{ image.displayName }}{%else%}{{ image.id }}{% endif %}</a>
|
||||
</td>
|
||||
<td class="image_location odd">
|
||||
{% if image.description %}
|
||||
{{ image.description }}
|
||||
{% else %}
|
||||
{{ image.location }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="image_owner_id">{{ image.ownerId }}</td>
|
||||
<td class="image_launch_btn odd"><a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}">Launch</a></td>
|
||||
{#<td class="odd"><a class="ui-state-default ui-corner-all" onclick="$('#dlg_launch').dialog('open');">Launch</a></td>#}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No images currently available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% load sidebar_tags %}
|
||||
|
||||
{% block nav_projects %}
|
||||
{% sidebar_select images %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
@ -0,0 +1,207 @@
|
||||
{% extends "django_nova/images/base.html" %}
|
||||
|
||||
{% block title %} - Launch an Image{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
<script type="text/javascript" src="/media/django_nova/js/jquery.form.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Images</h2>
|
||||
<p id="page_description">Images are snapshots of running systems which can easily be deployed to run one or more instances.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
{% if images %}
|
||||
<table id="image_launch">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Description</th>
|
||||
<th colspan="2">Owner</th>
|
||||
</tr>
|
||||
{% for image in images %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
{% if image.id == ami.id %}
|
||||
<td class="detail_wrapper" colspan="4">
|
||||
<div id="{{ ami.id }}" class="image_detail">
|
||||
<div class="column">
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Owner: </span>
|
||||
<span class="data">{{ ami.ownerId }}</span>
|
||||
</div>
|
||||
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Description: </span>
|
||||
<span class="data">{{ ami.description }}</span>
|
||||
</div>
|
||||
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Location: </span>
|
||||
<span class="data">{{ ami.location }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="image_detail_item">
|
||||
<span class="label">ID: </span>
|
||||
<span class="data">{{ ami.id }}</span>
|
||||
</div>
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Name: </span>
|
||||
<span class="data">{% if ami.displayName %}{{ ami.displayName }}{%else%}{{ ami.id }}{% endif %}</span>
|
||||
</div>
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Type: </span>
|
||||
<span class="data">{{ ami.type }}</span>
|
||||
</div>
|
||||
<div class="image_detail_item">
|
||||
<span class="label">Architecture: </span>
|
||||
<span class="data">{{ ami.architecture }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="last" class="column">
|
||||
{% if ami.is_public %}
|
||||
<div id="public" class="privacy">Public Image</div>
|
||||
{% else %}
|
||||
<div id="private" class="privacy">Private Image</div>
|
||||
{% endif %}
|
||||
|
||||
<a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}" title="Click to launch image">Launch</a>
|
||||
{% if can_modify or user.username == ami.ownerId %}
|
||||
<a id="edit_image_link" href="{% url nova_images_update project.projectname ami.id %}">Edit Image</a>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% if can_modify or user.username == ami.ownerId %}
|
||||
<span class="image_privacy">
|
||||
<form id="privacy_{{ ami.id }}" action="{% url nova_images_privacy project.projectname ami.id %}" method="post" accept-charset="utf-8">
|
||||
{% csrf_token %}
|
||||
{% if ami.is_public %}
|
||||
<input class="private" type="submit" value="Make Private" />
|
||||
{% else %}
|
||||
<input class="public" type="submit" value="Make Public" />
|
||||
{% endif %}
|
||||
</form>
|
||||
</span>
|
||||
|
||||
<span class="delete">
|
||||
<form id="delete_{{ ami.id }}" action="{% url nova_images_remove project.projectname ami.id %}" method="post" accept-charset="utf-8">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Remove Image" />
|
||||
</form>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="image_id"><a href="{% url nova_images_detail project.projectname image.id %}">{{ image.id }}</a></td>
|
||||
<td class="image_location odd">
|
||||
{% if image.description %}
|
||||
{{ image.description }}
|
||||
{% else %}
|
||||
{{ image.location }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="image_owner_id">{{ image.ownerId }}</td>
|
||||
<td class="image_launch_btn odd"><a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}">Launch</a></td>
|
||||
{#<td class="odd"><a class="ui-state-default ui-corner-all" onclick="$('#dlg_launch').dialog('open');">Launch</a></td>#}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No images currently available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="dlg_launch" title="Launch Instance" style="display:none;">
|
||||
<form id="frm_launch" action="url nova_images_launch project.projectname" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "django_nova/images/_launch_form.html" %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="dlg_confirm" title="Confirm Termination">
|
||||
<p>Are you sure you wish to unregister the <span id="ami_name"></span> image?</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
var options = {
|
||||
success: handleResponse,
|
||||
beforeSubmit: showRequest,
|
||||
dataType: 'json'
|
||||
}
|
||||
|
||||
// TODO: On dialog open, reset form and validation.
|
||||
$(function() {
|
||||
$('#dlg_launch').dialog({
|
||||
buttons: {
|
||||
'Ok': function() {
|
||||
$('#frm_launch').ajaxSubmit(options);
|
||||
},
|
||||
'Cancel': function() {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
autoOpen: false,
|
||||
resizable: false,
|
||||
width: 400,
|
||||
height: 400
|
||||
});
|
||||
});
|
||||
|
||||
function showRequest(formData, jqForm, options) {
|
||||
var queryString = $.param(formData);
|
||||
alert('About to submit: \n\n' + queryString);
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleResponse(data, statusText, xhr, $form) {
|
||||
alert('status: ' + statusText + '\nsuccess:\n\n' + data.success);
|
||||
}
|
||||
|
||||
$(function(){
|
||||
$('.delete form').submit(function() {
|
||||
ami_name = $(this).parent().parent().attr("id");
|
||||
$('#ami_name').text(ami_name);
|
||||
$('#dlg_confirm').dialog('open');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#dlg_confirm').dialog({
|
||||
buttons: {
|
||||
'Ok': onConfirmOK,
|
||||
'Cancel': function() { $(this).dialog('close'); }
|
||||
},
|
||||
autoOpen: false,
|
||||
resizable: false,
|
||||
width: 500,
|
||||
height: 200
|
||||
});
|
||||
})
|
||||
|
||||
function onConfirmOK() {
|
||||
$(this).dialog('close');
|
||||
form = document.getElementById('delete_' + ami_name);
|
||||
if(form) form.submit();
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,35 @@
|
||||
{% extends "django_nova/images/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
<script type="text/javascript" src="{{ COMMON_MEDIA_PREFIX }}js/jquery.form.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Edit Image</h2>
|
||||
<p id="page_description">From this page you can edit the name and description of an image that belongs to you.</p>
|
||||
</div>
|
||||
|
||||
<div class="dash_block first">
|
||||
<h3 class="image_id">Edit Image: {{ ami.id }}</h3>
|
||||
|
||||
<form class="edit_image" id="rename_{{ ami.id }}" action="{% url nova_images_update project.projectname ami.id %}" method="post">
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<input type="submit" value="Update Image" />
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,70 @@
|
||||
{% extends "django_nova/images/base.html" %}
|
||||
|
||||
{% block title %} - Launch an Image{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
<script type="text/javascript" src="/media/django_nova/js/jquery.form.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Images</h2>
|
||||
<p id="page_description">Images are snapshots of running systems which can easily be deployed to run one or more instances.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
{% for heading, images in image_lists.items %}
|
||||
{% include "django_nova/images/_list.html" %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
<div id="dlg_launch" title="Launch Instance" style="display:none;">
|
||||
<form id="frm_launch" action="#" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "django_nova/images/_launch_form.html" %}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
var options = {
|
||||
success: handleResponse,
|
||||
beforeSubmit: showRequest,
|
||||
dataType: 'json'
|
||||
}
|
||||
|
||||
// TODO: On dialog open, reset form and validation.
|
||||
$(function() {
|
||||
$('#dlg_launch').dialog({
|
||||
buttons: {
|
||||
'Ok': function() {
|
||||
$('#frm_launch').ajaxSubmit(options);
|
||||
},
|
||||
'Cancel': function() {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
},
|
||||
autoOpen: false,
|
||||
resizable: false,
|
||||
width: 400,
|
||||
height: 400
|
||||
});
|
||||
});
|
||||
|
||||
function showRequest(formData, jqForm, options) {
|
||||
var queryString = $.param(formData);
|
||||
alert('About to submit: \n\n' + queryString);
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleResponse(data, statusText, xhr, $form) {
|
||||
alert('status: ' + statusText + '\nsuccess:\n\n' + data.success);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,32 @@
|
||||
{% extends "django_nova/images/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
<script type="text/javascript" src="{{ COMMON_MEDIA_PREFIX }}js/jquery.form.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Launch Image</h2>
|
||||
<p id="page_description">You can launch up to five instances of an image at a time. Some images allow for custom configuration to be passed in via User data. (<a href="/kb/show/UserData/">read more</a>)</p>
|
||||
</div>
|
||||
|
||||
<div class="dash_block first">
|
||||
<form id="frm_launch" action="{% url nova_images_launch project.projectname ami.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<h3 class="image_id">Launch Image {{ ami.id }}</h3>
|
||||
<div class="even">
|
||||
<label>Location</label>
|
||||
<span class="image_location">{{ ami.location }}</span>
|
||||
</div>
|
||||
{% include "django_nova/images/_launch_form.html" %}
|
||||
<input id="launch_image" type="submit" value="Launch" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,104 @@
|
||||
{% if instances %}
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Image</th>
|
||||
<th>Size</th>
|
||||
<th>IP</th>
|
||||
<th colspan="2">State</th>
|
||||
</tr>
|
||||
{% for instance in instances %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
{% if instance.id == selected_instance.id %}
|
||||
<td class="detail_wrapper" colspan="6">
|
||||
<div id="{{selected_instance.id}}" class="instance_detail">
|
||||
<div class="column">
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">Instance ID: </span>
|
||||
<span class="data">{{ selected_instance.id }}</span>
|
||||
</div>
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">Name: </span>
|
||||
<span class="data">{% if selected_instance.displayName != "" %}{{ selected_instance.displayName }}{% else %} None {% endif %}</span>
|
||||
</div>
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">Description: </span>
|
||||
<span class="data" id="desc">{{ selected_instance.displayDescription }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">Region: </span>
|
||||
<span class="data">{{ selected_instance.region.name }}</span>
|
||||
</div>
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">Size: </span>
|
||||
<span class="data">{{ selected_instance.instance_type }}</span>
|
||||
</div>
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">State: </span>
|
||||
<span class="data">{{ selected_instance.state }}</span>
|
||||
</div>
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">Image ID: </span>
|
||||
<span class="data">{{ selected_instance.image_id }}</span>
|
||||
</div>
|
||||
<div class="instance_detail_item">
|
||||
<span class="label">IP Address: </span>
|
||||
<span class="data">{{ selected_instance.dns_name }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="last" class="column">
|
||||
{% if instance.state == "running" %}
|
||||
<a href="{% url nova_instances_console project.projectname instance.id %}" id="console_{{instance.id}}" class="console" target="_blank">Show Console</a>{% endif %}
|
||||
<a id="edit_instance_link" href="{% url nova_instance_update project.projectname instance.id %}">Edit Instance</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<span class="delete">
|
||||
<form id="form_terminate_{{ instance.id }}" class="form-terminate" method="post" action="{% url nova_instances_terminate project.projectname %}" >
|
||||
<input name="instance_id" type="hidden" value="{{ instance.id }}" />
|
||||
<input id="terminate_{{instance.id}}" class="terminate" type="submit" value="Terminate" />
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
</td>
|
||||
{% else %}
|
||||
|
||||
<td><a href="{% url nova_instances_detail project.projectname instance.id %}">{{ instance.id }} {% if instance.displayName %}({{ instance.displayName }}){% endif %}
|
||||
</a></td>
|
||||
<td class="odd">{{ instance.image_id }}</td>
|
||||
<td>{{ instance.instance_type }}</td>
|
||||
<td class="odd">{{ instance.dns_name }}</td>
|
||||
<td>{{ instance.state }}</td>
|
||||
<td id="actions" class="odd">
|
||||
<form id="form_terminate_{{ instance.id }}" class="form-terminate" method="post" action="{% url nova_instances_terminate project.projectname %}" >
|
||||
<input name="instance_id" type="hidden" value="{{ instance.id }}" />
|
||||
<input id="terminate_{{instance.id}}" class="terminate" type="submit" value="Terminate" />
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
{% if instance.state == "running" %}
|
||||
<a href="{% url nova_instances_console project.projectname instance.id %}" id="console_{{instance.id}}" class="console" target="_blank">Show Console</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No instances are currently running. You may start a new instance from the <a href="{% url nova_images project.projectname %}">images</a> tab.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% load sidebar_tags %}
|
||||
|
||||
{% block nav_projects %}
|
||||
{% sidebar_select instances %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
@ -0,0 +1,33 @@
|
||||
{% extends "django_nova/instances/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Instance ID: {{ instance.id }}</h2>
|
||||
<p id="page_description">Here you can see up to the minute performance data about your instance.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
|
||||
<div class="dash_block first">
|
||||
<h3 class="image_id">Edit Instance: {{ instance.id }}</h3>
|
||||
|
||||
<form class="edit_instance" id="rename_{{ instance.id }}" action="{% url nova_instance_update project.projectname instance.id %}" method="post">
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<input type="submit" value="Update Instance" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,34 @@
|
||||
{% extends "django_nova/instances/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
<script type="text/javascript" src="{{ COMMON_MEDIA_PREFIX }}js/jquery.form.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Edit Instance</h2>
|
||||
<p id="page_description">From this page you can give your instance an alias, so you don't have to remember its unique id.</p>
|
||||
</div>
|
||||
|
||||
<div class="dash_block first">
|
||||
<h3 class="image_id">Edit Instance: {{ instance.id }}</h3>
|
||||
|
||||
<form class="edit_instance" id="rename_{{ selected_instance.id }}" action="{% url nova_instance_update project.projectname instance.id %}" method="post">
|
||||
<fieldset>
|
||||
{% csrf_token %}
|
||||
{% for field in update_form %}
|
||||
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<input type="submit" value="Update Instance" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,98 @@
|
||||
{% extends "django_nova/instances/base.html" %}
|
||||
|
||||
{% block title %} - {{ project.projectname|capfirst }} Instances{% endblock %}
|
||||
{% block pageclass %}instances{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="page_head">
|
||||
<div id="spinner"></div>
|
||||
|
||||
<h2 id="page_heading">Instances</h2>
|
||||
<p id="page_description">Instances are virtual servers launched from images. You can launch instances from the <a href="{% url nova_images project.projectname %}">images tab</a>.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<div id="instances">
|
||||
{% include "django_nova/instances/_instances_list.html" %}
|
||||
</div>
|
||||
|
||||
<div id="dlg_confirm" title="Confirm Termination" style="display:none;">
|
||||
<p>Are you sure you wish to terminate instance <span id="spn_terminate"></span>?</p>
|
||||
</div>
|
||||
|
||||
<div id="connection_error" style="display:none;" title="Connection Error">
|
||||
<p><span class="ui-icon ui-icon-alert"></span>A connection error has occurred. Please ensure you are still connected to VPN.</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
|
||||
$(function() {
|
||||
setInterval(function() {
|
||||
$('#spinner').show();
|
||||
{% if detail %}
|
||||
$('#instances').load('{% url nova_instances_refresh_detail project.projectname selected_instance.id %}', onInstancesUpdated);
|
||||
{% else %}
|
||||
$('#instances').load('{% url nova_instances_refresh project.projectname %}', onInstancesUpdated);
|
||||
{% endif %}
|
||||
}, 15000);
|
||||
|
||||
initInstanceForms();
|
||||
|
||||
$('#dlg_confirm').dialog({
|
||||
buttons: {
|
||||
'Ok': onConfirmOK,
|
||||
'Cancel': function() { $(this).dialog('close'); }
|
||||
},
|
||||
autoOpen: false,
|
||||
resizable: false,
|
||||
width: 500,
|
||||
height: 200
|
||||
});
|
||||
});
|
||||
|
||||
var _terminateID = null;
|
||||
|
||||
function initInstanceForms() {
|
||||
$('.form-terminate').submit(function() {
|
||||
_terminateID = $(this).children(':first').val()
|
||||
$('#spn_terminate').text(_terminateID);
|
||||
$('#dlg_confirm').dialog('open');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function onInstancesUpdated(response, status, xhr) {
|
||||
$('#spinner').hide();
|
||||
|
||||
switch(xhr.status) {
|
||||
case 200:
|
||||
initInstanceForms();
|
||||
break;
|
||||
|
||||
case 403:
|
||||
document.location = '{% url auth_login %}';
|
||||
break;
|
||||
|
||||
default:
|
||||
$('#connection_error').dialog({
|
||||
dialogClass: 'alert',
|
||||
modal: true,
|
||||
closeOnEscape: true,
|
||||
buttons:{ "Close": function() { $(this).dialog("close"); } },
|
||||
});
|
||||
$('#connection_error').dialog('open');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onConfirmOK() {
|
||||
$(this).dialog('close');
|
||||
form = document.getElementById('form_terminate_' + _terminateID);
|
||||
if(form) form.submit();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,58 @@
|
||||
{% extends "django_nova/instances/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Instance ID: {{ instance.id }} Performance</h2>
|
||||
<p id="page_description">Here you can see up to the minute performance data about your instance.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<p>
|
||||
<h1>CPU Usage</h1>
|
||||
<h3>Today</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "cpu-1d.png" %}" />
|
||||
</p>
|
||||
<p>
|
||||
<h3>This Week</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "cpu-1w.png" %}" />
|
||||
</p>
|
||||
<p>
|
||||
<h3>This Month</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "cpu-1m.png" %}" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h1>Network Activity</h1>
|
||||
<h3>Today</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "net-1d.png" %}" />
|
||||
</p>
|
||||
<p>
|
||||
<h3>This Week</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "net-1w.png" %}" />
|
||||
</p>
|
||||
<p>
|
||||
<h3>This Month</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "net-1m.png" %}" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h1>Disk Activity</h3>
|
||||
<h3>Today</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "disk-1d.png" %}" />
|
||||
</p>
|
||||
<p>
|
||||
<h3>This Week</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "disk-1w.png" %}" />
|
||||
</p>
|
||||
<p>
|
||||
<h3>This Month</h3>
|
||||
<img src="{% url nova_instances_graph project.projectname instance.id "disk-1m.png" %}" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,5 @@
|
||||
{% for field in create_form %}
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
@ -0,0 +1,31 @@
|
||||
{% if keypairs %}
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<th>Key Pair Name</th>
|
||||
<th>Fingerprint</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% for keypair in keypairs %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td>{{ keypair.name }}</td>
|
||||
<td class="odd">{{ keypair.fingerprint }}</td>
|
||||
<td>
|
||||
<form id="form_key_delete_{{keypair.name}}" class="form-key-delete" method="post" action="{% url nova_keypairs_delete project.projectname %}">
|
||||
<input name="key_name" type="hidden" value="{{ keypair.name }}" />
|
||||
<input id="keypair_delete_{{keypair.name}}" class="delete" type="submit" value="Delete" />
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No key pairs currently exist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% load sidebar_tags %}
|
||||
|
||||
{% block nav_projects %}
|
||||
{% sidebar_select keys %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
@ -0,0 +1,77 @@
|
||||
{% extends "django_nova/keypairs/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="/media/dashboard/js/jquery.form.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Keys</h2>
|
||||
<p id="page_description">Key pairs are ssh credentials which are injected into images when they are launched. Creating a new key pair registers the public key and downloads the private key (a pem file). <em>Protect and use the key as a normal private key.</em></p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<div id="instances">
|
||||
{% include "django_nova/keypairs/_list.html" %}
|
||||
</div>
|
||||
|
||||
<div class="dash_block first">
|
||||
<form id="frm_key_create" action="{% url nova_keypairs_add project.projectname %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input id="js" name="js" type="hidden" value="0" />
|
||||
<fieldset>
|
||||
<h3>Create New Keypair</h3>
|
||||
{% include "django_nova/keypairs/_create_form.html" %}
|
||||
<input id="keypair_create" class="create" type="submit" value="Create" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="dlg_confirm" title="Confirm Termination">
|
||||
<p>Are you sure you wish to delete key <span id="spn_delete_key_name"></span>?</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block footerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
$(function() { $('#js').val('1'); });
|
||||
|
||||
{% if download_key %}
|
||||
$(function() { window.location = '{% url nova_keypairs_download project.projectname download_key %}'; });
|
||||
{% endif %}
|
||||
|
||||
$(function() {
|
||||
$('.form-key-delete').submit(function() {
|
||||
_key_name = $(this).children(':first').val()
|
||||
$('#spn_delete_key_name').text(_key_name);
|
||||
$('#dlg_confirm').dialog('open');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#dlg_confirm').dialog({
|
||||
buttons: {
|
||||
'Ok': onConfirmOK,
|
||||
'Cancel': function() { $(this).dialog('close'); }
|
||||
},
|
||||
autoOpen: false,
|
||||
resizable: false,
|
||||
width: 500,
|
||||
height: 200
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var _terminateID = null;
|
||||
|
||||
function onConfirmOK() {
|
||||
$(this).dialog('close');
|
||||
form = document.getElementById('form_key_delete_' + _key_name);
|
||||
if(form) form.submit();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -0,0 +1,72 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %}
|
||||
{% block pageclass %}overview{% endblock %}
|
||||
|
||||
{% block headerjs %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(function(){
|
||||
$.each($("#user_edit form select"), function () {
|
||||
// "Locations" can be any label you want
|
||||
SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
|
||||
});
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Edit User Roles</h2>
|
||||
<p id="page_description">From here you can edit multiple user roles.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
<div id="user_edit" class="dash_block first">
|
||||
{% if user %}
|
||||
|
||||
<form action="." method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||
<h3 id="edit_{{ user.username }}">Edit Roles for User: {{ user.username }}</h3>
|
||||
<div class="form-row">
|
||||
<label>User</label>
|
||||
<span id="user_name">{{ user.username }}</span>
|
||||
</div>
|
||||
<input type="hidden" name="username" value="{{ user.id }}" id="username" />
|
||||
|
||||
{% for field in form.visible_fields %}
|
||||
<div class="form-row">
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}{{ field }}
|
||||
{% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<div class="cancel">
|
||||
<a href="{% url nova_project_manage project.projectname %}">Cancel</a>
|
||||
</div>
|
||||
<div class="submit-row">
|
||||
<input type="submit" value="Save" class="default" />
|
||||
{# <a href="#" class="deletelink">Remove User</a> #}
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No users are currently associated with this project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,26 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %}
|
||||
{% block pageclass %}overview{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="page_head">
|
||||
<h2><span>{{ project.projectname|capfirst }}</span> Overview</h2>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<div id="welcome">
|
||||
<p>Welcome to the <span>{{ project.projectname|capfirst }}</span> Overview. From here you can manage your instances, images, keys, and security groups.</p>
|
||||
<p>To get started using the command line management tools, you can <a target="_blank" href="http://open.eucalyptus.com/wiki/Euca2oolsGuide_v1.1">download euca2ools</a> and use them with your x509 credentials.</p>
|
||||
</div>
|
||||
|
||||
<div id="resources" class="dash_block">
|
||||
<h3>Project Resources</h3>
|
||||
<ul>
|
||||
<li><a href="{% url nova_download_credentials project.projectname %}">Generate X509 credentials.</a></li>
|
||||
<li><a href="{% url nova_instances project.projectname %}">View Instances (<strong>{{ instance_count }}</strong> running).</a></li>
|
||||
<li><a href="{% url nova_images project.projectname %}">View Images.</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,45 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %}
|
||||
{% block pageclass %}overview{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Manage Users and Roles</h2>
|
||||
<p id="page_description">From here you can manage users and roles.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<div id="users">
|
||||
{% if members %}
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Project Roles</th>
|
||||
<th>Global Roles</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% for member in members %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td>{{ member.memberId }} {% if project.projectManagerId == member.memberId %}(<em>project manager</em>){% endif %}</td>
|
||||
<td>{{ member.project_roles }}</td>
|
||||
<td>{{ member.global_roles }}</td>
|
||||
<td class="odd">
|
||||
<a href="{% url nova_project_edit_user project.projectname member.memberId%}">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No users are currently associated with this project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,5 @@
|
||||
{% for field in authorize_form %}
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
@ -0,0 +1,5 @@
|
||||
{% for field in create_form %}
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
@ -0,0 +1,3 @@
|
||||
<input type="hidden" name="protocol" value="{{ rule.ip_protocol }}" />
|
||||
<input type="hidden" name="from_port" value="{{ rule.from_port}}" />
|
||||
<input type="hidden" name="to_port" value="{{ rule.to_port }}" />
|
@ -0,0 +1,7 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% load sidebar_tags %}
|
||||
|
||||
{% block nav_projects %}
|
||||
{% sidebar_select securitygroups %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
@ -0,0 +1,62 @@
|
||||
{% extends "django_nova/securitygroups/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="dashboard_tabs">
|
||||
<div id="tabs-1" class="ui-tabs-panel ui-widget-content ui-corner-bottom dash-wrap" style="margin-left:0;min-height:300px;">
|
||||
<ul id="dashboard_nav">
|
||||
<li><a id="lnk_overview" href="{% url dashboard_project project.projectname %}">Overview</a></li>
|
||||
<li><a id="lnk_instances" href="{% url dashboard_instances project.projectname %}">Instances</a></li>
|
||||
<li><a id="lnk_images" href="{% url dashboard_images project.projectname %}">Images</a></li>
|
||||
<li><a id="lnk_keypairs" href="{% url dashboard_keypairs project.projectname %}">Keys</a></li>
|
||||
<li class="active"><a id="lnk_securitygroups" href="{% url dashboard_securitygroups project.projectname %}">Security Groups</a></li>
|
||||
<li><a id="lnk_volumes" href="{% url dashboard_volumes project.projectname %}">Volumes</a></li>
|
||||
</ul>
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2>Security Group: {{ securitygroup.name }}</h2>
|
||||
<p>Add and remove protocols to the security group by authorizing and revoking port forwarding. For instance<br /> [tcp, 80, 80] will allow access to HTTP from devices outside this security group.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Protocol</th>
|
||||
<th>From Port</th>
|
||||
<th>To Port</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for rule in securitygroup.rules %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td>{{ rule.ip_protocol }}</td>
|
||||
<td class="odd">{{ rule.from_port }}</td>
|
||||
<td>{{ rule.to_port }}</td>
|
||||
<td class="odd">
|
||||
<form id="security_groups" method="post" action="{% url dashboard_securitygroups_revoke project.projectname securitygroup.name %}">
|
||||
{% csrf_token %}
|
||||
{% include "django_nova/securitygroups/_revoke_form.html" %}
|
||||
<input class="ui-state-default ui-corner-all" type="submit" value="Revoke" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div class="block">
|
||||
<h3>Authorize</h3>
|
||||
<form id="authorize" method="post" action="{% url dashboard_securitygroups_authorize project.projectname securitygroup.name %}">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<input type="hidden" name="group" value="{{ securitygroup.name }}" />
|
||||
{% include "django_nova/securitygroups/_authorize_form.html" %}
|
||||
<input class="ui-state-default ui-corner-all" type="submit" value="Authorize">
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,59 @@
|
||||
{% extends "django_nova/securitygroups/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="dashboard_tabs">
|
||||
<div id="tabs-1" class="ui-tabs-panel ui-widget-content ui-corner-bottom dash-wrap" style="margin-left:0px;min-height:300px;">
|
||||
<ul id="dashboard_nav">
|
||||
<li><a id="lnk_overview" href="{% url dashboard_project project.projectname %}">Overview</a></li>
|
||||
<li><a id="lnk_instances" href="{% url dashboard_instances project.projectname %}">Instances</a></li>
|
||||
<li><a id="lnk_images" href="{% url dashboard_images project.projectname %}">Images</a></li>
|
||||
<li><a id="lnk_keypairs" href="{% url dashboard_keypairs project.projectname %}">Keys</a></li>
|
||||
<li class="active"><a id="lnk_securitygroups" href="{% url dashboard_securitygroups project.projectname %}">Security Groups</a></li>
|
||||
<li><a id="lnk_volumes" href="{% url dashboard_volumes project.projectname %}">Volumes</a></li>
|
||||
</ul>
|
||||
<div id="right_content">
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Security Groups</h2>
|
||||
<p id="page_description">Security groups are firewall rules which allow access to your instances from other groups as well as the internet. All ports/protocols are denied by default.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
<table style="width:100%;">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="min-width:60%;">Description</th>
|
||||
<th>Rules</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% for securitygroup in securitygroups %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td id="group_{{ securitygroup.id }}"><a href="{% url dashboard_securitygroups_detail project.projectname securitygroup.name %}">{{ securitygroup.name }}</a></td>
|
||||
<td id="group_{{ securitygroup.id }}_description" class="odd">{{ securitygroup.description }}</td>
|
||||
<td id="group_{{ securitygroup.id }}_rules">{{ securitygroup.rules|length }}</td>
|
||||
<td class="odd">
|
||||
<form id="delete_group_{{ securitygroup.id }}" method="post" action="{% url dashboard_securitygroups_delete project.projectname securitygroup.name %}">
|
||||
{% csrf_token %}
|
||||
<input class="ui-state-default ui-corner-all" type="submit" value="Delete">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="block">
|
||||
<form id="add_group_form" method="post" action="{% url dashboard_securitygroups_add project.projectname %}">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<h3>New Group</h3>
|
||||
{% include "django_nova/securitygroups/_create_form.html" %}
|
||||
<label> </label><input class="ui-state-default ui-corner-all" type="submit" value="Create" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clr"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,5 @@
|
||||
{% for field in attach_form %}
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
@ -0,0 +1,5 @@
|
||||
{% for field in create_form %}
|
||||
{{ field.label_tag }}
|
||||
{% if field.errors %}{{ field.errors }}{% endif %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
@ -0,0 +1,7 @@
|
||||
{% extends "django_nova/base.html" %}
|
||||
{% load sidebar_tags %}
|
||||
|
||||
{% block nav_projects %}
|
||||
{% sidebar_select volumes %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
@ -0,0 +1,84 @@
|
||||
{% extends "django_nova/volumes/base.html" %}
|
||||
|
||||
{% block title %} - Cloud Computing{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="page_head">
|
||||
<h2 id="page_heading">Volumes</h2>
|
||||
<p id="page_description">Volumes provide persistent block storage. Creating a new volume gives you a raw block device which you may format with your choice of filesystems (ext3 is recommended). A volume may only be attached to a single instance at a time.</p>
|
||||
</div>
|
||||
|
||||
{% include "django_nova/_messages.html" %}
|
||||
|
||||
{% if volumes %}
|
||||
<table style="width: 100%">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Size</th>
|
||||
<th colspan="2">Status</th>
|
||||
</tr>
|
||||
{% for volume in volumes %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td id="volume_{{ volume.id }}">{{ volume.id }} {{ volume.displayName }}</td>
|
||||
<td id="volume_{{ volume.id }}_size" class="odd">{{ volume.size }}GB</td>
|
||||
<td id="volume_{{ volume.id }})_status">
|
||||
{% if volume.status == "in-use" %}
|
||||
{% if volume.attachment_state == "attached" %}
|
||||
attached: {{ volume.attach_data.instance_id }}
|
||||
{% else %}
|
||||
{{ volume.attachment_state }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ volume.status }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if volume.attachment_state == "attached" %}
|
||||
<td class="odd">
|
||||
<form class="volume" action="{% url nova_volumes_detach project.projectname volume.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input id="detach_{{ volume.id }}" class="detach" type="submit" value="Detach">
|
||||
</form>
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="odd">
|
||||
<form class="volume" action="{% url nova_volumes_delete project.projectname volume.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input id="delete_{{ volume.id }}" class="delete" type="submit" value="Delete">
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="ui-widget">
|
||||
<div class="ui-state-highlight ui-corner-all">
|
||||
<p>
|
||||
<span class="ui-icon ui-icon-info"></span>
|
||||
No volumes currently exist.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="dash_block first">
|
||||
<form id="new_volume_form" method="post" action="{% url nova_volumes_add project.projectname %}">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<h3>Create New Volume</h3>
|
||||
{% include "django_nova/volumes/_create_form.html" %}
|
||||
<input id="create_volume" class="create" type="submit" value="Create" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="dash_block">
|
||||
<form id="new_volume_form" method="post" action="{% url nova_volumes_attach project.projectname %}">
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
<h3>Attach Volume</h3>
|
||||
{% include "django_nova/volumes/_attach_form.html" %}
|
||||
<input id="attach_volume" class="attach" type="submit" value="Attach" />
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
50
django-nova/src/django_nova/templatetags/admin_extras.py
Normal file
50
django-nova/src/django_nova/templatetags/admin_extras.py
Normal file
@ -0,0 +1,50 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Template tags for extending the Django admin interface.
|
||||
"""
|
||||
|
||||
from django.contrib.admin.templatetags.admin_list import items_for_result, result_headers
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import Library
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
register = Library()
|
||||
|
||||
def project_result_list(cl):
|
||||
headers = list(result_headers(cl))
|
||||
headers.append({'text': mark_safe(' ')})
|
||||
|
||||
results = list()
|
||||
|
||||
for project in cl.result_list:
|
||||
rl = list(items_for_result(cl,project,None))
|
||||
|
||||
url = reverse('admin_project_sendcredentials', args=[project.projectname])
|
||||
content = mark_safe('<td><a href="%s">Send Credentials</a></td>' % url)
|
||||
|
||||
rl.append(content)
|
||||
results.append(rl)
|
||||
|
||||
return {
|
||||
'cl': cl,
|
||||
'result_headers': headers,
|
||||
'results': results
|
||||
}
|
||||
project_result_list = register.inclusion_tag("admin/change_list_results.html")(project_result_list)
|
37
django-nova/src/django_nova/templatetags/django_nova_tags.py
Normal file
37
django-nova/src/django_nova/templatetags/django_nova_tags.py
Normal file
@ -0,0 +1,37 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Template tags for working with django_nova.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class SiteBrandingNode(template.Node):
|
||||
def render(self, context):
|
||||
return settings.SITE_BRANDING
|
||||
|
||||
@register.tag
|
||||
def site_branding(parser, token):
|
||||
return SiteBrandingNode()
|
||||
|
39
django-nova/src/django_nova/templatetags/project_tags.py
Normal file
39
django-nova/src/django_nova/templatetags/project_tags.py
Normal file
@ -0,0 +1,39 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Template tags for gathering contextual region data.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django_nova.shortcuts import get_projects
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class ProjectsNode(template.Node):
|
||||
def render(self, context):
|
||||
# Store project list in template context.
|
||||
context['projects'] = get_projects(context['request'].user)
|
||||
return ''
|
||||
|
||||
|
||||
@register.tag
|
||||
def load_projects(parser, token):
|
||||
return ProjectsNode()
|
40
django-nova/src/django_nova/templatetags/region_tags.py
Normal file
40
django-nova/src/django_nova/templatetags/region_tags.py
Normal file
@ -0,0 +1,40 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Template tags for gathering contextual region data.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django_nova.shortcuts import get_current_region, get_all_regions
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class RegionsNode(template.Node):
|
||||
def render(self, context):
|
||||
# Store region info in template context.
|
||||
context['current_region'] = get_current_region(context['request'])
|
||||
context['regions'] = get_all_regions()
|
||||
return ''
|
||||
|
||||
|
||||
@register.tag
|
||||
def load_regions(parser, token):
|
||||
return RegionsNode()
|
||||
|
46
django-nova/src/django_nova/templatetags/sidebar_tags.py
Normal file
46
django-nova/src/django_nova/templatetags/sidebar_tags.py
Normal file
@ -0,0 +1,46 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Template tags for rendering the sidebar.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class SidebarSelectNode(template.Node):
|
||||
def __init__(self, selected):
|
||||
self.selected = selected
|
||||
|
||||
def render(self, context):
|
||||
# Store page type in template context.
|
||||
context['sidebar_selected'] = self.selected
|
||||
return ''
|
||||
|
||||
|
||||
@register.tag
|
||||
def sidebar_select(parser, token):
|
||||
try:
|
||||
tag_name, selected = token.split_contents()
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0]
|
||||
return SidebarSelectNode(str(selected))
|
||||
|
31
django-nova/src/django_nova/templatetags/truncate_filter.py
Normal file
31
django-nova/src/django_nova/templatetags/truncate_filter.py
Normal file
@ -0,0 +1,31 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
Template tags for truncating strings.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter("truncate")
|
||||
def truncate(value, size):
|
||||
if len(value) > size and size > 3:
|
||||
return value[0:(size-3)] + '...'
|
||||
else:
|
||||
return value[0:size]
|
1
django-nova/src/django_nova/tests/__init__.py
Normal file
1
django-nova/src/django_nova/tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from view_tests import *
|
36
django-nova/src/django_nova/tests/urls.py
Normal file
36
django-nova/src/django_nova/tests/urls.py
Normal file
@ -0,0 +1,36 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
URL patterns for testing django-nova views.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^projects/', include('django_nova.urls.project')),
|
||||
url(r'^region/', include('django_nova.urls.region')),
|
||||
url(r'^admin/projects/', include('django_nova.urls.admin_project')),
|
||||
url(r'^admin/roles/', include('django_nova.urls.admin_roles')),
|
||||
url(r'^credentials/download/(?P<auth_token>\w+)/$',
|
||||
'django_nova.views.credentials.authorize_credentials',
|
||||
name='nova_credentials_authorize'),
|
||||
)
|
||||
|
7
django-nova/src/django_nova/tests/view_tests/__init__.py
Normal file
7
django-nova/src/django_nova/tests/view_tests/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
from credential_tests import *
|
||||
from image_tests import *
|
||||
from instance_tests import *
|
||||
from keypair_tests import *
|
||||
from region_tests import *
|
||||
from volume_tests import *
|
||||
|
90
django-nova/src/django_nova/tests/view_tests/base.py
Normal file
90
django-nova/src/django_nova/tests/view_tests/base.py
Normal file
@ -0,0 +1,90 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Base classes for view based unit tests.
|
||||
"""
|
||||
|
||||
import mox
|
||||
|
||||
from django import test
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django_nova import adminclient
|
||||
from django_nova import manager
|
||||
from django_nova import shortcuts
|
||||
|
||||
|
||||
TEST_PROJECT = 'test'
|
||||
TEST_USER = 'test'
|
||||
TEST_REGION = 'test'
|
||||
|
||||
|
||||
class BaseViewTests(test.TestCase):
|
||||
def setUp(self):
|
||||
self.mox = mox.Mox()
|
||||
|
||||
def tearDown(self):
|
||||
self.mox.UnsetStubs()
|
||||
|
||||
def assertRedirectsNoFollow(self, response, expected_url):
|
||||
self.assertEqual(response._headers['location'],
|
||||
('Location', settings.TESTSERVER + expected_url))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def authenticateTestUser(self):
|
||||
user = auth_models.User.objects.create_user(TEST_USER,
|
||||
'test@test.com',
|
||||
password='test')
|
||||
login = self.client.login(username=TEST_USER, password='test')
|
||||
self.failUnless(login, 'Unable to login')
|
||||
return user
|
||||
|
||||
|
||||
class BaseProjectViewTests(BaseViewTests):
|
||||
def setUp(self):
|
||||
super(BaseProjectViewTests, self).setUp()
|
||||
|
||||
project = adminclient.ProjectInfo()
|
||||
project.projectname = TEST_PROJECT
|
||||
project.projectManagerId = TEST_USER
|
||||
|
||||
self.user = self.authenticateTestUser()
|
||||
self.region = adminclient.RegionInfo(name=TEST_REGION,
|
||||
endpoint='http://test:8773/')
|
||||
self.project = manager.ProjectManager(self.user.username,
|
||||
project,
|
||||
self.region)
|
||||
self.mox.StubOutWithMock(shortcuts, 'get_project_or_404')
|
||||
shortcuts.get_project_or_404(mox.IgnoreArg(),
|
||||
'test').AndReturn(self.project)
|
||||
|
||||
def create_key_pair_choices(self, key_names):
|
||||
return [(k, k) for k in key_names]
|
||||
|
||||
def create_instance_type_choices(self):
|
||||
return [('m1.medium', 'm1.medium'),
|
||||
('m1.large', 'm1.large')]
|
||||
|
||||
def create_instance_choices(self, instance_ids):
|
||||
return [(id, id) for id in instance_ids]
|
||||
|
||||
def create_available_volume_choices(self, volumes):
|
||||
return [(v.id, '%s %s - %dGB' % (v.id, v.displayName, v.size)) \
|
||||
for v in volumes]
|
||||
|
@ -0,0 +1,70 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for credential views.
|
||||
"""
|
||||
|
||||
import mox
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_nova import models
|
||||
from django_nova.tests.view_tests.base import BaseViewTests
|
||||
|
||||
|
||||
class CredentialViewTests(BaseViewTests):
|
||||
def test_download_expired_credentials(self):
|
||||
auth_token = 'expired'
|
||||
self.mox.StubOutWithMock(models.CredentialsAuthorization,
|
||||
'get_by_token')
|
||||
models.CredentialsAuthorization.get_by_token(auth_token) \
|
||||
.AndReturn(None)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_credentials_authorize',
|
||||
args=[auth_token]))
|
||||
self.assertTemplateUsed(res, 'django_nova/credentials/expired.html')
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_download_good_credentials(self):
|
||||
auth_token = 'good'
|
||||
|
||||
creds = models.CredentialsAuthorization()
|
||||
creds.username = 'test'
|
||||
creds.project = 'test'
|
||||
creds.auth_token = auth_token
|
||||
|
||||
self.mox.StubOutWithMock(models.CredentialsAuthorization,
|
||||
'get_by_token')
|
||||
self.mox.StubOutWithMock(creds, 'get_zip')
|
||||
models.CredentialsAuthorization.get_by_token(auth_token) \
|
||||
.AndReturn(creds)
|
||||
creds.get_zip().AndReturn('zip')
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_credentials_authorize',
|
||||
args=[auth_token]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res['Content-Disposition'],
|
||||
'attachment; filename=%s-test-test-x509.zip' %
|
||||
settings.SITE_NAME)
|
||||
self.assertContains(res, 'zip')
|
||||
|
||||
self.mox.VerifyAll()
|
232
django-nova/src/django_nova/tests/view_tests/image_tests.py
Normal file
232
django-nova/src/django_nova/tests/view_tests/image_tests.py
Normal file
@ -0,0 +1,232 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for image views.
|
||||
"""
|
||||
|
||||
import boto.ec2.image
|
||||
import boto.ec2.instance
|
||||
import mox
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_nova import forms
|
||||
from django_nova import shortcuts
|
||||
from django_nova.tests.view_tests.base import BaseProjectViewTests, TEST_PROJECT
|
||||
|
||||
|
||||
TEST_IMAGE_ID = 'ami_test'
|
||||
TEST_INSTANCE_ID = 'i-abcdefg'
|
||||
TEST_KEY = 'foo'
|
||||
|
||||
|
||||
class ImageViewTests(BaseProjectViewTests):
|
||||
def setUp(self):
|
||||
self.ami = boto.ec2.image.Image()
|
||||
self.ami.id = TEST_IMAGE_ID
|
||||
setattr(self.ami, 'displayName', TEST_IMAGE_ID)
|
||||
setattr(self.ami, 'description', TEST_IMAGE_ID)
|
||||
super(ImageViewTests, self).setUp()
|
||||
|
||||
def test_index(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_images')
|
||||
self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
|
||||
self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
|
||||
|
||||
self.project.get_images().AndReturn([])
|
||||
forms.get_key_pair_choices(self.project).AndReturn([])
|
||||
forms.get_instance_type_choices().AndReturn([])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_images', args=[TEST_PROJECT]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/images/index.html')
|
||||
self.assertEqual(len(res.context['image_lists']), 3)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_launch_form(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
|
||||
self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
|
||||
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
forms.get_key_pair_choices(self.project).AndReturn([])
|
||||
forms.get_instance_type_choices().AndReturn([])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
args = [TEST_PROJECT, TEST_IMAGE_ID]
|
||||
res = self.client.get(reverse('nova_images_launch', args=args))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/images/launch.html')
|
||||
self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_launch(self):
|
||||
instance = boto.ec2.instance.Instance()
|
||||
instance.id = TEST_INSTANCE_ID
|
||||
instance.image_id = TEST_IMAGE_ID
|
||||
reservation = boto.ec2.instance.Reservation()
|
||||
reservation.instances = [instance]
|
||||
|
||||
self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
|
||||
self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
|
||||
self.mox.StubOutWithMock(self.project, 'run_instances')
|
||||
|
||||
forms.get_key_pair_choices(self.project).AndReturn(
|
||||
self.create_key_pair_choices([TEST_KEY]))
|
||||
forms.get_instance_type_choices().AndReturn(
|
||||
self.create_instance_type_choices())
|
||||
self.project.run_instances(TEST_IMAGE_ID,
|
||||
addressing_type=mox.IgnoreArg(),
|
||||
key_name=TEST_KEY,
|
||||
user_data='',
|
||||
instance_type='m1.medium',
|
||||
min_count='1',
|
||||
max_count='1').AndReturn(reservation)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('nova_images_launch', args=[TEST_PROJECT, TEST_IMAGE_ID])
|
||||
data = {'key_name': TEST_KEY,
|
||||
'count': '1',
|
||||
'size': 'm1.medium',
|
||||
'user_data': ''}
|
||||
res = self.client.post(url, data)
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_instances',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_detail(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_images')
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.mox.StubOutWithMock(shortcuts, 'get_user_image_permissions')
|
||||
self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
|
||||
self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
|
||||
|
||||
self.project.get_images().AndReturn([self.ami])
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
shortcuts.get_user_image_permissions(mox.IgnoreArg(),
|
||||
TEST_PROJECT).AndReturn(True)
|
||||
forms.get_key_pair_choices(self.project).AndReturn(
|
||||
self.create_key_pair_choices([TEST_KEY]))
|
||||
forms.get_instance_type_choices().AndReturn(
|
||||
self.create_instance_type_choices())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_images_detail',
|
||||
args=[TEST_PROJECT, TEST_IMAGE_ID]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/images/index.html')
|
||||
self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_remove_form(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_images_remove',
|
||||
args=[TEST_PROJECT, TEST_IMAGE_ID]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/images/detail_list.html')
|
||||
self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_remove(self):
|
||||
self.mox.StubOutWithMock(self.project, 'deregister_image')
|
||||
self.project.deregister_image(TEST_IMAGE_ID).AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('nova_images_remove',
|
||||
args=[TEST_PROJECT, TEST_IMAGE_ID]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_images',
|
||||
args=[TEST_PROJECT]))
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_make_public(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.mox.StubOutWithMock(self.project, 'modify_image_attribute')
|
||||
|
||||
self.ami.is_public = False
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
self.project.modify_image_attribute(TEST_IMAGE_ID,
|
||||
attribute='launchPermission',
|
||||
operation='add').AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('nova_images_privacy',
|
||||
args=[TEST_PROJECT, TEST_IMAGE_ID]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_images_detail',
|
||||
args=[TEST_PROJECT, TEST_IMAGE_ID]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_make_private(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.mox.StubOutWithMock(self.project, 'modify_image_attribute')
|
||||
|
||||
self.ami.is_public = True
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
self.project.modify_image_attribute(TEST_IMAGE_ID,
|
||||
attribute='launchPermission',
|
||||
operation='remove').AndReturn(True)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
args = [TEST_PROJECT, TEST_IMAGE_ID]
|
||||
res = self.client.post(reverse('nova_images_privacy', args=args))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_images_detail',
|
||||
args=args))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_update_form(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
args = [TEST_PROJECT, TEST_IMAGE_ID]
|
||||
res = self.client.get(reverse('nova_images_update', args=args))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/images/edit.html')
|
||||
self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_update(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_image')
|
||||
self.mox.StubOutWithMock(self.project, 'update_image')
|
||||
|
||||
self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
|
||||
self.project.update_image(TEST_IMAGE_ID, 'test', 'test').AndReturn(True)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
args = [TEST_PROJECT, TEST_IMAGE_ID]
|
||||
data = {'nickname': 'test',
|
||||
'description': 'test'}
|
||||
url = reverse('nova_images_update', args=args)
|
||||
res = self.client.post(url, data)
|
||||
expected_url = reverse('nova_images_detail', args=args)
|
||||
self.assertRedirectsNoFollow(res, expected_url)
|
||||
|
||||
self.mox.VerifyAll()
|
@ -0,0 +1,67 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for instance views.
|
||||
"""
|
||||
|
||||
import boto.ec2.instance
|
||||
import mox
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_nova.tests.view_tests.base import BaseProjectViewTests, TEST_PROJECT
|
||||
|
||||
|
||||
TEST_INSTANCE_ID = 'i-abcdefgh'
|
||||
|
||||
|
||||
class InstanceViewTests(BaseProjectViewTests):
|
||||
def test_index(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_instances')
|
||||
self.project.get_instances().AndReturn([])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_instances', args=[TEST_PROJECT]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/instances/index.html')
|
||||
self.assertEqual(len(res.context['instances']), 0)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_detail(self):
|
||||
instance = boto.ec2.instance.Instance()
|
||||
instance.id = TEST_INSTANCE_ID
|
||||
instance.displayName = instance.id
|
||||
instance.displayDescription = instance.id
|
||||
|
||||
self.mox.StubOutWithMock(self.project, 'get_instance')
|
||||
self.project.get_instance(instance.id).AndReturn(instance)
|
||||
self.mox.StubOutWithMock(self.project, 'get_instances')
|
||||
self.project.get_instances().AndReturn([instance])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_instances_detail',
|
||||
args=[TEST_PROJECT, TEST_INSTANCE_ID]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTemplateUsed(res, 'django_nova/instances/index.html')
|
||||
self.assertEqual(res.context['selected_instance'].id, instance.id)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
@ -0,0 +1,93 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for key pair views.
|
||||
"""
|
||||
|
||||
import boto.ec2.keypair
|
||||
import mox
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_nova.tests.view_tests.base import (BaseProjectViewTests,
|
||||
TEST_PROJECT)
|
||||
|
||||
|
||||
TEST_KEY = 'test_key'
|
||||
|
||||
|
||||
class KeyPairViewTests(BaseProjectViewTests):
|
||||
def test_index(self):
|
||||
self.mox.StubOutWithMock(self.project, 'get_key_pairs')
|
||||
self.project.get_key_pairs().AndReturn([])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
response = self.client.get(reverse('nova_keypairs',
|
||||
args=[TEST_PROJECT]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'django_nova/keypairs/index.html')
|
||||
self.assertEqual(len(response.context['keypairs']), 0)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_add_keypair(self):
|
||||
key = boto.ec2.keypair.KeyPair()
|
||||
key.name = TEST_KEY
|
||||
|
||||
self.mox.StubOutWithMock(self.project, 'create_key_pair')
|
||||
self.project.create_key_pair(key.name).AndReturn(key)
|
||||
self.mox.StubOutWithMock(self.project, 'has_key_pair')
|
||||
self.project.has_key_pair(key.name).AndReturn(False)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('nova_keypairs_add', args=[TEST_PROJECT])
|
||||
data = {'js': '0', 'name': key.name}
|
||||
res = self.client.post(url, data)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res['Content-Type'], 'application/binary')
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_delete_keypair(self):
|
||||
self.mox.StubOutWithMock(self.project, 'delete_key_pair')
|
||||
self.project.delete_key_pair(TEST_KEY).AndReturn(None)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
data = {'key_name': TEST_KEY}
|
||||
url = reverse('nova_keypairs_delete', args=[TEST_PROJECT])
|
||||
res = self.client.post(url, data)
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_keypairs',
|
||||
args=[TEST_PROJECT]))
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_download_keypair(self):
|
||||
material = 'abcdefgh'
|
||||
session = self.client.session
|
||||
session['key.%s' % TEST_KEY] = material
|
||||
session.save()
|
||||
|
||||
res = self.client.get(reverse('nova_keypairs_download',
|
||||
args=['test', TEST_KEY]))
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(res['Content-Type'], 'application/binary')
|
||||
self.assertContains(res, material)
|
||||
|
43
django-nova/src/django_nova/tests/view_tests/region_tests.py
Normal file
43
django-nova/src/django_nova/tests/view_tests/region_tests.py
Normal file
@ -0,0 +1,43 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for region views.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_nova.tests.view_tests.base import BaseViewTests
|
||||
from django_nova import shortcuts
|
||||
|
||||
|
||||
TEST_REGION = 'one'
|
||||
|
||||
|
||||
class RegionViewTests(BaseViewTests):
|
||||
def test_change(self):
|
||||
self.authenticateTestUser()
|
||||
session = self.client.session
|
||||
session['region'] = 'two'
|
||||
session.save()
|
||||
|
||||
data = {'redirect_url': '/',
|
||||
'region': TEST_REGION}
|
||||
res = self.client.post(reverse('region_change'), data)
|
||||
self.assertEqual(self.client.session['region'], TEST_REGION)
|
||||
self.assertRedirectsNoFollow(res, '/')
|
||||
|
170
django-nova/src/django_nova/tests/view_tests/volume_tests.py
Normal file
170
django-nova/src/django_nova/tests/view_tests/volume_tests.py
Normal file
@ -0,0 +1,170 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Unit tests for volume views.
|
||||
"""
|
||||
|
||||
import boto.ec2.volume
|
||||
import mox
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django_nova import forms
|
||||
from django_nova.tests.view_tests.base import (BaseProjectViewTests,
|
||||
TEST_PROJECT)
|
||||
|
||||
|
||||
TEST_VOLUME = 'vol-0000001'
|
||||
|
||||
|
||||
class VolumeTests(BaseProjectViewTests):
|
||||
def test_index(self):
|
||||
instance_id = 'i-abcdefgh'
|
||||
|
||||
volume = boto.ec2.volume.Volume()
|
||||
volume.id = TEST_VOLUME
|
||||
volume.displayName = TEST_VOLUME
|
||||
volume.size = 1
|
||||
|
||||
self.mox.StubOutWithMock(self.project, 'get_volumes')
|
||||
self.mox.StubOutWithMock(forms, 'get_available_volume_choices')
|
||||
self.mox.StubOutWithMock(forms, 'get_instance_choices')
|
||||
self.project.get_volumes().AndReturn([])
|
||||
forms.get_available_volume_choices(mox.IgnoreArg()).AndReturn(
|
||||
self.create_available_volume_choices([volume]))
|
||||
forms.get_instance_choices(mox.IgnoreArg()).AndReturn(
|
||||
self.create_instance_choices([instance_id]))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
response = self.client.get(reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, 'django_nova/volumes/index.html')
|
||||
self.assertEqual(len(response.context['volumes']), 0)
|
||||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_add_get(self):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_volumes_add', args=[TEST_PROJECT]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_add_post(self):
|
||||
vol = boto.ec2.volume.Volume()
|
||||
vol.name = TEST_VOLUME
|
||||
vol.displayName = TEST_VOLUME
|
||||
vol.size = 1
|
||||
|
||||
self.mox.StubOutWithMock(self.project, 'create_volume')
|
||||
self.project.create_volume(vol.size, vol.name, vol.name).AndReturn(vol)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('nova_volumes_add', args=[TEST_PROJECT])
|
||||
data = {'size': '1',
|
||||
'nickname': TEST_VOLUME,
|
||||
'description': TEST_VOLUME}
|
||||
res = self.client.post(url, data)
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_delete_get(self):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_volumes_delete',
|
||||
args=[TEST_PROJECT, TEST_VOLUME]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_delete_post(self):
|
||||
self.mox.StubOutWithMock(self.project, 'delete_volume')
|
||||
self.project.delete_volume(TEST_VOLUME).AndReturn(True)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('nova_volumes_delete',
|
||||
args=[TEST_PROJECT, TEST_VOLUME]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_attach_get(self):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_volumes_attach',
|
||||
args=[TEST_PROJECT]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_attach_post(self):
|
||||
volume = boto.ec2.volume.Volume()
|
||||
volume.id = TEST_VOLUME
|
||||
volume.displayName = TEST_VOLUME
|
||||
volume.size = 1
|
||||
|
||||
instance_id = 'i-abcdefgh'
|
||||
device = '/dev/vdb'
|
||||
|
||||
self.mox.StubOutWithMock(self.project, 'attach_volume')
|
||||
self.mox.StubOutWithMock(forms, 'get_available_volume_choices')
|
||||
self.mox.StubOutWithMock(forms, 'get_instance_choices')
|
||||
self.project.attach_volume(TEST_VOLUME, instance_id, device) \
|
||||
.AndReturn(True)
|
||||
forms.get_available_volume_choices(mox.IgnoreArg()).AndReturn(
|
||||
self.create_available_volume_choices([volume]))
|
||||
forms.get_instance_choices(mox.IgnoreArg()).AndReturn(
|
||||
self.create_instance_choices([instance_id]))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('nova_volumes_attach', args=[TEST_PROJECT])
|
||||
data = {'volume': TEST_VOLUME,
|
||||
'instance': instance_id,
|
||||
'device': device}
|
||||
res = self.client.post(url, data)
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_detach_get(self):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('nova_volumes_detach',
|
||||
args=[TEST_PROJECT, TEST_VOLUME]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def test_detach_post(self):
|
||||
self.mox.StubOutWithMock(self.project, 'detach_volume')
|
||||
self.project.detach_volume(TEST_VOLUME).AndReturn(True)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('nova_volumes_detach',
|
||||
args=[TEST_PROJECT, TEST_VOLUME]))
|
||||
self.assertRedirectsNoFollow(res, reverse('nova_volumes',
|
||||
args=[TEST_PROJECT]))
|
||||
self.mox.VerifyAll()
|
21
django-nova/src/django_nova/testsettings.py
Normal file
21
django-nova/src/django_nova/testsettings.py
Normal file
@ -0,0 +1,21 @@
|
||||
import os
|
||||
|
||||
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
DEBUG = True
|
||||
TESTSERVER = 'http://testserver'
|
||||
DATABASE_ENGINE = 'sqlite3'
|
||||
DATABASE_NAME = '/tmp/django-nova.db'
|
||||
INSTALLED_APPS = ['django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django_nova']
|
||||
ROOT_URLCONF = 'django_nova.tests.urls'
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(ROOT_PATH, 'tests', 'templates')
|
||||
)
|
||||
SITE_BRANDING = 'OpenStack'
|
||||
SITE_NAME = 'openstack'
|
||||
NOVA_DEFAULT_ENDPOINT = 'none'
|
||||
NOVA_DEFAULT_REGION = 'test'
|
||||
NOVA_ACCESS_KEY = 'test'
|
||||
NOVA_SECRET_KEY = 'test'
|
0
django-nova/src/django_nova/urls/__init__.py
Normal file
0
django-nova/src/django_nova/urls/__init__.py
Normal file
55
django-nova/src/django_nova/urls/admin_project.py
Normal file
55
django-nova/src/django_nova/urls/admin_project.py
Normal file
@ -0,0 +1,55 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
URL patterns for managing Nova projects through the Django admin interface.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
#TODO(devcamcar): Standardize url names admin_project_*.
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$',
|
||||
'django_nova.views.admin.projects_list',
|
||||
name='admin_projects'),
|
||||
url(r'^add/$',
|
||||
'django_nova.views.admin.add_project',
|
||||
name='add_project'),
|
||||
url(r'^(?P<project_name>[^/]+)/$',
|
||||
'django_nova.views.admin.project_view',
|
||||
name='admin_project'),
|
||||
url(r'^(?P<project_name>[^/]+)/user/(?P<project_user>[^/]+)/delete/',
|
||||
'django_nova.views.admin.delete_project_user',
|
||||
name='admin_project_delete_user'),
|
||||
url(r'^(?P<project_name>[^/]+)/delete/$',
|
||||
'django_nova.views.admin.delete_project',
|
||||
name='delete_project'),
|
||||
url(r'^(?P<project_name>[^/]+)/user/add/$',
|
||||
'django_nova.views.admin.add_project_user',
|
||||
name='add_project_user'),
|
||||
url(r'^(?P<project_name>[^/]+)/user/(?P<project_user>[^/]+)/$',
|
||||
'django_nova.views.admin.project_user',
|
||||
name='project_user'),
|
||||
url(r'^(?P<project_id>[^/]+)/sendcredentials/$',
|
||||
'django_nova.views.admin.project_sendcredentials',
|
||||
name='admin_project_sendcredentials'),
|
||||
url(r'^(?P<project_id>[^/]+)/start_vpn/$',
|
||||
'django_nova.views.admin.project_start_vpn',
|
||||
name='admin_project_start_vpn'),
|
||||
)
|
32
django-nova/src/django_nova/urls/admin_roles.py
Normal file
32
django-nova/src/django_nova/urls/admin_roles.py
Normal file
@ -0,0 +1,32 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
URL patterns for managing Nova user roles through the Django admin interface.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<user_id>[^/]+)/$',
|
||||
'django_nova.views.admin.user_roles',
|
||||
name='admin_user_roles'),
|
||||
url(r'^$',
|
||||
'django_nova.views.admin.users_list',
|
||||
name='admin_users_list'),
|
||||
)
|
129
django-nova/src/django_nova/urls/project.py
Normal file
129
django-nova/src/django_nova/urls/project.py
Normal file
@ -0,0 +1,129 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
URL patterns for managing Nova projects.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^(?P<project_id>[^/]+)/$',
|
||||
'django_nova.views.projects.detail',
|
||||
name='nova_project'),
|
||||
url(r'^(?P<project_id>[^/]+)/manage/(?P<username>[^/]+)/',
|
||||
'django_nova.views.projects.edit_user',
|
||||
name='nova_project_edit_user'),
|
||||
url(r'^(?P<project_id>[^/]+)/manage$',
|
||||
'django_nova.views.projects.manage',
|
||||
name='nova_project_manage'),
|
||||
url(r'^(?P<project_id>[^/]+)/download/credentials$',
|
||||
'django_nova.views.projects.download_credentials',
|
||||
name='nova_download_credentials'),
|
||||
url(r'^(?P<project_id>[^/]+)/images$',
|
||||
'django_nova.views.images.index',
|
||||
name='nova_images'),
|
||||
url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/launch$',
|
||||
'django_nova.views.images.launch',
|
||||
name='nova_images_launch'),
|
||||
url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/remove$',
|
||||
'django_nova.views.images.remove',
|
||||
name='nova_images_remove'),
|
||||
url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/update$',
|
||||
'django_nova.views.images.update',
|
||||
name='nova_images_update'),
|
||||
url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/detail$',
|
||||
'django_nova.views.images.detail',
|
||||
name='nova_images_detail'),
|
||||
url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)$',
|
||||
'django_nova.views.images.privacy',
|
||||
name='nova_images_privacy'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances$',
|
||||
'django_nova.views.instances.index',
|
||||
name='nova_instances'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/refresh$',
|
||||
'django_nova.views.instances.refresh',
|
||||
name='nova_instances_refresh'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/refresh$',
|
||||
'django_nova.views.instances.refresh_detail',
|
||||
name='nova_instances_refresh_detail'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/terminate$',
|
||||
'django_nova.views.instances.terminate',
|
||||
name='nova_instances_terminate'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)$',
|
||||
'django_nova.views.instances.detail',
|
||||
name='nova_instances_detail'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/performance$',
|
||||
'django_nova.views.instances.performance',
|
||||
name='nova_instances_performance'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/console$',
|
||||
'django_nova.views.instances.console',
|
||||
name='nova_instances_console'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>.*)/update$',
|
||||
'django_nova.views.instances.update',
|
||||
name='nova_instance_update'),
|
||||
url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/graph/(?P<graph_name>[^/]+)$',
|
||||
'django_nova.views.instances.graph',
|
||||
name='nova_instances_graph'),
|
||||
url(r'^(?P<project_id>[^/]+)/keys$',
|
||||
'django_nova.views.keypairs.index',
|
||||
name='nova_keypairs'),
|
||||
url(r'^(?P<project_id>[^/]+)/keys/add$',
|
||||
'django_nova.views.keypairs.add',
|
||||
name='nova_keypairs_add'),
|
||||
url(r'^(?P<project_id>[^/]+)/keys/delete$',
|
||||
'django_nova.views.keypairs.delete',
|
||||
name='nova_keypairs_delete'),
|
||||
url(r'^(?P<project_id>[^/]+)/keys/(?P<key_name>.*)/download$',
|
||||
'django_nova.views.keypairs.download',
|
||||
name='nova_keypairs_download'),
|
||||
#url(r'^(?P<project_id>[^/]+)/securitygroups/$',
|
||||
# 'django_nova.views.securitygroups.index',
|
||||
# name='nova_securitygroups'),
|
||||
#url(r'^(?P<project_id>[^/]+)/securitygroups/add$',
|
||||
# 'django_nova.views.securitygroups.add',
|
||||
# name='nova_securitygroups_add'),
|
||||
#url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>[^/]+)$',
|
||||
# 'django_nova.views.securitygroups.detail',
|
||||
# name='nova_securitygroups_detail'),
|
||||
#url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>[^/]+)/authorize/$',
|
||||
# 'django_nova.views.securitygroups.authorize',
|
||||
# name='nova_securitygroups_authorize'),
|
||||
#url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>[^/]+)/delete/$',
|
||||
# 'django_nova.views.securitygroups.delete',
|
||||
# name='nova_securitygroups_delete'),
|
||||
#url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>.*)/revoke/$',
|
||||
# 'django_nova.views.securitygroups.revoke',
|
||||
# name='nova_securitygroups_revoke'),
|
||||
url(r'^(?P<project_id>[^/]+)/volumes/$',
|
||||
'django_nova.views.volumes.index',
|
||||
name='nova_volumes'),
|
||||
url(r'^(?P<project_id>[^/]+)/volumes/add$',
|
||||
'django_nova.views.volumes.add',
|
||||
name='nova_volumes_add'),
|
||||
url(r'^(?P<project_id>[^/]+)/volumes/attach$',
|
||||
'django_nova.views.volumes.attach',
|
||||
name='nova_volumes_attach'),
|
||||
url(r'^(?P<project_id>[^/]+)/volumes/(?P<volume_id>[^/]+)/detach$',
|
||||
'django_nova.views.volumes.detach',
|
||||
name='nova_volumes_detach'),
|
||||
url(r'^(?P<project_id>[^/]+)/volumes/(?P<volume_id>[^/]+)/delete$',
|
||||
'django_nova.views.volumes.delete',
|
||||
name='nova_volumes_delete'),
|
||||
)
|
29
django-nova/src/django_nova/urls/region.py
Normal file
29
django-nova/src/django_nova/urls/region.py
Normal file
@ -0,0 +1,29 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
"""
|
||||
URL patterns for managing Nova regions.
|
||||
"""
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^change/$',
|
||||
'django_nova.views.regions.change',
|
||||
name='region_change'),
|
||||
)
|
0
django-nova/src/django_nova/views/__init__.py
Normal file
0
django-nova/src/django_nova/views/__init__.py
Normal file
326
django-nova/src/django_nova/views/admin.py
Normal file
326
django-nova/src/django_nova/views/admin.py
Normal file
@ -0,0 +1,326 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova through the Django admin interface.
|
||||
"""
|
||||
|
||||
import boto.exception
|
||||
from django import http
|
||||
from django import template
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.contrib.auth import models as auth_models
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import forms
|
||||
from django_nova import models
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def project_sendcredentials(request, project_id):
|
||||
nova = get_nova_admin_connection()
|
||||
project = nova.get_project(project_id)
|
||||
|
||||
users = [user.memberId for user in nova.get_project_members(project_id)]
|
||||
form = forms.SendCredentialsForm(query_list=users)
|
||||
|
||||
if project == None:
|
||||
raise http.Http404()
|
||||
|
||||
if request.method == 'POST':
|
||||
if len(request.POST.getlist('users')) < 1:
|
||||
msg = "Please select a user to send credentials to."
|
||||
|
||||
return render_to_response('admin/django_nova/project/send_credentials.html', {
|
||||
'project' : project,
|
||||
'form' : form,
|
||||
'users' : users,
|
||||
'error': msg,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
else:
|
||||
for username in request.POST.getlist('users'):
|
||||
models.CredentialsAuthorization.authorize(username, project_id)
|
||||
msg = "Credentials were successfully sent."
|
||||
return render_to_response('admin/django_nova/project/send_credentials.html', {
|
||||
'project' : project,
|
||||
'form' : form,
|
||||
'users' : users,
|
||||
'success': msg,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
return render_to_response('admin/django_nova/project/send_credentials.html', {
|
||||
'project' : project,
|
||||
'form' : form,
|
||||
'users' : users,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def project_start_vpn(request, project_id):
|
||||
nova = get_nova_admin_connection()
|
||||
project = nova.get_project(project_id)
|
||||
|
||||
if project == None:
|
||||
raise http.Http404()
|
||||
|
||||
try:
|
||||
nova.start_vpn(project_id)
|
||||
messages.success(request,
|
||||
'Successfully started VPN for project %s.' %
|
||||
project_id)
|
||||
except boto.exception.EC2ResponseError, e:
|
||||
messages.error(request,
|
||||
'Unable to start VPN for the project %s: %s - %s' %
|
||||
(project_id, e.code, e.error_message))
|
||||
|
||||
return redirect('admin_projects')
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def projects_list(request):
|
||||
nova = get_nova_admin_connection()
|
||||
projects = nova.get_projects()
|
||||
|
||||
return render_to_response('admin/django_nova/project/project_list.html', {
|
||||
'projects' : projects
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def project_view(request, project_name):
|
||||
nova = get_nova_admin_connection()
|
||||
project = nova.get_project(project_name)
|
||||
users = nova.get_project_members(project_name)
|
||||
try:
|
||||
manager = auth_models.User.objects.get(username=project.projectManagerId)
|
||||
except auth_models.User.DoesNotExist:
|
||||
manager = None
|
||||
|
||||
for user in users:
|
||||
project_role = [str(role.role) for role in nova.get_user_roles(user.memberId, project_name)]
|
||||
global_role = [str(role.role) for role in nova.get_user_roles(user.memberId, project=False)]
|
||||
|
||||
user.project_roles = ", ".join(project_role)
|
||||
user.global_roles = ", ".join(global_role)
|
||||
|
||||
return render_to_response('admin/django_nova/project/edit_project.html', {
|
||||
'project' : project,
|
||||
'users' : users,
|
||||
'projectname': project.projectname,
|
||||
'manager': manager,
|
||||
'description': project.description,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def add_project(request):
|
||||
nova = get_nova_admin_connection()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.ProjectForm(request.POST)
|
||||
if form.is_valid():
|
||||
manager = form.cleaned_data["manager"]
|
||||
nova.create_project(form.cleaned_data["projectname"],
|
||||
manager.username,
|
||||
form.cleaned_data["description"])
|
||||
return redirect('admin_project', request.POST["projectname"])
|
||||
else:
|
||||
form = forms.ProjectForm()
|
||||
|
||||
return render_to_response('admin/django_nova/project/add_project.html', {
|
||||
'form' : form,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def delete_project(request, project_name):
|
||||
nova = get_nova_admin_connection()
|
||||
|
||||
if request.method == 'POST':
|
||||
nova.delete_project(project_name)
|
||||
return redirect('admin_projects')
|
||||
|
||||
project = nova.get_project(project_name)
|
||||
|
||||
return render_to_response('admin/django_nova/project/delete_project.html', {
|
||||
'project' : project,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
def remove_project_roles(username, project):
|
||||
nova = get_nova_admin_connection()
|
||||
userroles = nova.get_user_roles(username, project)
|
||||
roles = [str(role.role) for role in userroles]
|
||||
|
||||
for role in roles:
|
||||
if role == "developer":
|
||||
nova.remove_user_role(username, "developer", project)
|
||||
if role == "sysadmin":
|
||||
nova.remove_user_role(username, "sysadmin", project)
|
||||
if role == "netadmin":
|
||||
nova.remove_user_role(username, "netadmin", project)
|
||||
|
||||
def remove_global_roles(username):
|
||||
nova = get_nova_admin_connection()
|
||||
userroles = nova.get_user_roles(username)
|
||||
roles = [str(role.role) for role in userroles]
|
||||
|
||||
for role in roles:
|
||||
if role == "developer":
|
||||
nova.remove_user_role(username, "developer")
|
||||
if role == "sysadmin":
|
||||
nova.remove_user_role(username, "sysadmin")
|
||||
if role == "netadmin":
|
||||
nova.remove_user_role(username, "netadmin")
|
||||
if role == "cloudadmin":
|
||||
nova.remove_user_role(username, "cloudadmin")
|
||||
if role == "itsec":
|
||||
nova.remove_user_role(username, "itsec")
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def project_user(request, project_name, project_user):
|
||||
nova = get_nova_admin_connection()
|
||||
userroles = nova.get_user_roles(project_user, project_name)
|
||||
try:
|
||||
modeluser = auth_models.User.objects.get(username = project_user)
|
||||
except auth_models.User.DoesNotExist:
|
||||
modeluser = None
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.ProjectUserForm(request.POST)
|
||||
if form.is_valid():
|
||||
username = project_user
|
||||
|
||||
# hacky work around to interface correctly with multiple select form
|
||||
remove_project_roles(username, project_name)
|
||||
|
||||
roleform = request.POST.getlist("role")
|
||||
for role in roleform:
|
||||
nova.add_user_role(username, str(role), project_name)
|
||||
|
||||
return redirect('admin_project', project_name)
|
||||
else:
|
||||
roles = [str(role.role) for role in userroles]
|
||||
form = forms.ProjectUserForm({
|
||||
'role': roles,
|
||||
'user': modeluser,
|
||||
})
|
||||
|
||||
project = nova.get_project(project_name)
|
||||
|
||||
return render_to_response('admin/django_nova/project/project_user.html', {
|
||||
'form' : form,
|
||||
'project' : project,
|
||||
'user': modeluser,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def add_project_user(request, project_name):
|
||||
nova = get_nova_admin_connection()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.AddProjectUserForm(request.POST, project=project_name)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data["username"].username
|
||||
nova.add_project_member(username, project_name,)
|
||||
|
||||
roleform = request.POST.getlist("role")
|
||||
for role in roleform:
|
||||
nova.add_user_role(username, str(role), project_name)
|
||||
|
||||
return redirect('admin_project', project_name)
|
||||
else:
|
||||
form = forms.AddProjectUserForm(project=project_name)
|
||||
|
||||
project = nova.get_project(project_name)
|
||||
|
||||
return render_to_response('admin/django_nova/project/add_project_user.html', {
|
||||
'form' : form,
|
||||
'project' : project,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def delete_project_user(request, project_name, project_user):
|
||||
nova = get_nova_admin_connection()
|
||||
|
||||
if request.method == 'POST':
|
||||
nova.remove_project_member(project_user, project_name)
|
||||
return redirect('admin_project', project_name)
|
||||
|
||||
project = nova.get_project(project_name)
|
||||
user = nova.get_user(project_user)
|
||||
|
||||
return render_to_response('admin/django_nova/project/delete_project_user.html', {
|
||||
'user' : user,
|
||||
'project' : project,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def users_list(request):
|
||||
nova = get_nova_admin_connection()
|
||||
users = nova.get_users()
|
||||
|
||||
for user in users:
|
||||
# NOTE(devcamcar): Temporarily disabled for performance reasons.
|
||||
#roles = [str(role.role) for role in nova.get_user_roles(user.username)]
|
||||
roles = []
|
||||
user.roles = ", ".join(roles)
|
||||
|
||||
return render_to_response('admin/django_nova/project/user_list.html', {
|
||||
'users' : users
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def user_roles(request, user_id):
|
||||
nova = get_nova_admin_connection()
|
||||
userroles = nova.get_user_roles(user_id)
|
||||
try:
|
||||
modeluser = auth_models.User.objects.get(username=user_id)
|
||||
except auth_models.User.DoesNotExist:
|
||||
modeluser = None
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.GlobalRolesForm(request.POST)
|
||||
if form.is_valid():
|
||||
username = user_id
|
||||
|
||||
# hacky work around to interface correctly with multiple select form
|
||||
remove_global_roles(username)
|
||||
|
||||
roleform = request.POST.getlist("role")
|
||||
for role in roleform:
|
||||
nova.add_user_role(username, str(role))
|
||||
|
||||
return redirect('admin_user_roles', user_id)
|
||||
else:
|
||||
roles = [str(role.role) for role in userroles]
|
||||
form = forms.GlobalRolesForm({
|
||||
'username': modeluser and modeluser.id or None,
|
||||
'role': roles,
|
||||
})
|
||||
|
||||
return render_to_response('admin/django_nova/project/global_edit_user.html', {
|
||||
'form' : form,
|
||||
'user' : modeluser,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
46
django-nova/src/django_nova/views/credentials.py
Normal file
46
django-nova/src/django_nova/views/credentials.py
Normal file
@ -0,0 +1,46 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for downloading X509 credentials. Useful when using an invitation
|
||||
style system for configuring first time users.
|
||||
"""
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render_to_response
|
||||
from django_nova import models
|
||||
|
||||
|
||||
def authorize_credentials(request, auth_token):
|
||||
"""Sends X509 credentials to user if their auth token is valid."""
|
||||
auth_token = auth_token.lower()
|
||||
credentials = models.CredentialsAuthorization.get_by_token(auth_token)
|
||||
|
||||
# NOTE(devcamcar): If nothing returned, then token was bad or has expired.
|
||||
if not credentials:
|
||||
return render_to_response('django_nova/credentials/expired.html')
|
||||
|
||||
response = http.HttpResponse(mimetype='application/zip')
|
||||
response['Content-Disposition'] = \
|
||||
'attachment; filename=%s-%s-%s-x509.zip' % \
|
||||
(settings.SITE_NAME, credentials.project, credentials.username)
|
||||
response.write(credentials.get_zip())
|
||||
|
||||
return response
|
||||
|
229
django-nova/src/django_nova/views/images.py
Normal file
229
django-nova/src/django_nova/views/images.py
Normal file
@ -0,0 +1,229 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova images.
|
||||
"""
|
||||
|
||||
import boto.exception
|
||||
import re
|
||||
|
||||
from django import http
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import exceptions
|
||||
from django_nova import forms
|
||||
from django_nova import shortcuts
|
||||
from django_nova.exceptions import handle_nova_error
|
||||
|
||||
|
||||
def _image_lists(images, project_id):
|
||||
def image_is_project(i):
|
||||
return i.ownerId == project_id
|
||||
|
||||
def image_is_admin(i):
|
||||
return i.ownerId in ['admin']
|
||||
|
||||
def image_is_community(i):
|
||||
return (not image_is_admin(i)) and (not image_is_project(i))
|
||||
|
||||
return {'Project Images': filter(image_is_project, images),
|
||||
'%s Images' % settings.SITE_BRANDING: filter(image_is_admin,
|
||||
images),
|
||||
'Community Images': filter(image_is_community, images)}
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def index(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
images = project.get_images()
|
||||
|
||||
return render_to_response('django_nova/images/index.html', {
|
||||
'form': forms.LaunchInstanceForm(project),
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'image_lists': _image_lists(images, project_id),
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def launch(request, project_id, image_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.LaunchInstanceForm(project, request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
reservation = project.run_instances(
|
||||
image_id,
|
||||
addressing_type='private',
|
||||
key_name=form.cleaned_data['key_name'],
|
||||
#security_groups=[form.cleaned_data['security_group']],
|
||||
user_data=re.sub('\r\n', '\n',
|
||||
form.cleaned_data['user_data']),
|
||||
instance_type=form.cleaned_data['size'],
|
||||
min_count=form.cleaned_data['count'],
|
||||
max_count=form.cleaned_data['count']
|
||||
)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to launch: %s' % e.message)
|
||||
else:
|
||||
for instance in reservation.instances:
|
||||
messages.success(request,
|
||||
'Instance %s launched.' % instance.id)
|
||||
return redirect('nova_instances', project_id)
|
||||
else:
|
||||
form = forms.LaunchInstanceForm(project)
|
||||
|
||||
ami = project.get_image(image_id)
|
||||
|
||||
return render_to_response('django_nova/images/launch.html', {
|
||||
'form': form,
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'ami': ami,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def detail(request, project_id, image_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
images = project.get_images()
|
||||
|
||||
ami = project.get_image(image_id)
|
||||
can_modify = shortcuts.get_user_image_permissions(request.user.username,
|
||||
project_id)
|
||||
|
||||
if not ami:
|
||||
raise http.Http404()
|
||||
return render_to_response('django_nova/images/index.html', {
|
||||
'form': forms.LaunchInstanceForm(project),
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'images': images,
|
||||
'image_lists': _image_lists(images, project_id),
|
||||
'ami': ami,
|
||||
'can_modify': can_modify,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def remove(request, project_id, image_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
project.deregister_image(image_id)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to deregister image: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Image %s has been successfully deregistered.' %
|
||||
image_id)
|
||||
|
||||
return redirect('nova_images', project_id)
|
||||
else:
|
||||
ami = project.get_image(image_id)
|
||||
|
||||
#FIXME - is the code below used? if we reach here should we
|
||||
#just redirect? (anthony)
|
||||
return render_to_response('django_nova/images/detail_list.html', {
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'ami': ami,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def privacy(request, project_id, image_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
ami = project.get_image(image_id)
|
||||
|
||||
if ami.is_public:
|
||||
try:
|
||||
project.modify_image_attribute(image_id,
|
||||
attribute='launchPermission',
|
||||
operation='remove')
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to make image private: %s' % e.message)
|
||||
else:
|
||||
try:
|
||||
project.modify_image_attribute(image_id,
|
||||
attribute='launchPermission',
|
||||
operation='add')
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to make image public: %s' % e.message)
|
||||
|
||||
return redirect('nova_images_detail', project_id, image_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def update(request, project_id, image_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
ami = project.get_image(image_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.UpdateImageForm(ami, request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
project.update_image(image_id,
|
||||
form.cleaned_data['nickname'],
|
||||
form.cleaned_data['description'])
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to update image: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Image %s has been updated.' % image_id)
|
||||
|
||||
return redirect('nova_images_detail', project_id, image_id)
|
||||
|
||||
# TODO(devcamcar): This needs to be cleaned up. Can make
|
||||
# one of the render_to_response blocks go away.
|
||||
else:
|
||||
form = forms.UpdateImageForm(ami)
|
||||
return render_to_response('django_nova/images/edit.html', {
|
||||
'form': form,
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'ami': ami,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
else:
|
||||
form = forms.UpdateImageForm(ami)
|
||||
return render_to_response('django_nova/images/edit.html', {
|
||||
'form': form,
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'ami': ami,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
203
django-nova/src/django_nova/views/instances.py
Normal file
203
django-nova/src/django_nova/views/instances.py
Normal file
@ -0,0 +1,203 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova instances.
|
||||
"""
|
||||
|
||||
from django import http
|
||||
from django import template
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import exceptions
|
||||
from django_nova import forms as nova_forms
|
||||
from django_nova import shortcuts
|
||||
from django_nova.exceptions import handle_nova_error
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def index(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
|
||||
|
||||
return render_to_response('django_nova/instances/index.html', {
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'instances': instances,
|
||||
'detail' : False,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def detail(request, project_id, instance_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
instance = project.get_instance(instance_id)
|
||||
instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
|
||||
|
||||
if not instance:
|
||||
raise http.Http404()
|
||||
|
||||
return render_to_response('django_nova/instances/index.html', {
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'selected_instance': instance,
|
||||
'instances': instances,
|
||||
'update_form': nova_forms.UpdateInstanceForm(instance),
|
||||
'detail' : True,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def performance(request, project_id, instance_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
instance = project.get_instance(instance_id)
|
||||
|
||||
if not instance:
|
||||
raise http.Http404()
|
||||
|
||||
return render_to_response('django_nova/instances/performance.html', {
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'instance': instance,
|
||||
'update_form': nova_forms.UpdateInstanceForm(instance),
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
# TODO(devcamcar): Wrap this in an @ajax decorator.
|
||||
def refresh(request, project_id):
|
||||
# TODO(devcamcar): This logic belongs in decorator.
|
||||
if not request.user.is_authenticated():
|
||||
return http.HttpResponseForbidden()
|
||||
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
|
||||
|
||||
return render_to_response('django_nova/instances/_instances_list.html', {
|
||||
'project': project,
|
||||
'instances': instances,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@handle_nova_error
|
||||
def refresh_detail(request, project_id, instance_id):
|
||||
# TODO(devcamcar): This logic belongs in decorator.
|
||||
if not request.user.is_authenticated():
|
||||
return http.HttpResponseForbidden()
|
||||
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
instance = project.get_instance(instance_id)
|
||||
instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
|
||||
|
||||
return render_to_response('django_nova/instances/_instances_list.html', {
|
||||
'project': project,
|
||||
'selected_instance': instance,
|
||||
'instances': instances,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def terminate(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
instance_id = request.POST['instance_id']
|
||||
|
||||
try:
|
||||
project.terminate_instance(instance_id)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to terminate %s: %s' %
|
||||
(instance_id, e.message,))
|
||||
else:
|
||||
messages.success(request,
|
||||
'Instance %s has been terminated.' % instance_id)
|
||||
|
||||
return redirect('nova_instances', project_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def console(request, project_id, instance_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
conn = project.get_nova_connection()
|
||||
console = conn.get_console_output(instance_id)
|
||||
response = http.HttpResponse(mimetype='text/plain')
|
||||
response.write(console.output)
|
||||
response.flush()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def graph(request, project_id, instance_id, graph_name):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
graph = project.get_instance_graph(instance_id, graph_name)
|
||||
|
||||
if graph is None:
|
||||
raise http.Http404()
|
||||
|
||||
response = http.HttpResponse(mimetype='image/png')
|
||||
response.write(graph)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def update(request, project_id, instance_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
instance = project.get_instance(instance_id)
|
||||
|
||||
if not instance:
|
||||
raise http.Http404()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = nova_forms.UpdateInstanceForm(instance, request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
project.update_instance(instance_id, form.cleaned_data)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to update instance %s: %s' %
|
||||
(instance_id, e.message,))
|
||||
else:
|
||||
messages.success(request,
|
||||
'Instance %s has been updated.' % instance_id)
|
||||
return redirect('nova_instances', project_id)
|
||||
else:
|
||||
return render_to_response('django_nova/instances/edit.html', {
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'instance': instance,
|
||||
'update_form': form,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
else:
|
||||
return render_to_response('django_nova/instances/edit.html', {
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'instance': instance,
|
||||
'update_form': nova_forms.UpdateInstanceForm(instance),
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
122
django-nova/src/django_nova/views/keypairs.py
Normal file
122
django-nova/src/django_nova/views/keypairs.py
Normal file
@ -0,0 +1,122 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova keypairs.
|
||||
"""
|
||||
|
||||
from django import http
|
||||
from django import template
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import exceptions
|
||||
from django_nova import forms
|
||||
from django_nova import shortcuts
|
||||
from django_nova.exceptions import handle_nova_error
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def index(request, project_id, download_key=None):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
keypairs = project.get_key_pairs()
|
||||
|
||||
return render_to_response('django_nova/keypairs/index.html', {
|
||||
'create_form': forms.CreateKeyPairForm(project),
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'keypairs': keypairs,
|
||||
'download_key': download_key
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def add(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.CreateKeyPairForm(project, request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
try:
|
||||
keypair = project.create_key_pair(form.cleaned_data['name'])
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to create key: %s' % e.message)
|
||||
else:
|
||||
if request.POST['js'] == '1':
|
||||
request.session['key.%s' % keypair.name] = keypair.material
|
||||
return index(request,
|
||||
project_id,
|
||||
download_key=keypair.name)
|
||||
else:
|
||||
response = http.HttpResponse(mimetype='application/binary')
|
||||
response['Content-Disposition'] = \
|
||||
'attachment; filename=%s.pem' % \
|
||||
form.cleaned_data['name']
|
||||
response.write(keypair.material)
|
||||
return response
|
||||
else:
|
||||
keypairs = project.get_key_pairs()
|
||||
|
||||
return render_to_response('django_nova/keypairs/index.html', {
|
||||
'create_form': form,
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'keypairs': keypairs,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
return redirect('nova_keypairs', project_id)
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def delete(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
key_name = request.POST['key_name']
|
||||
|
||||
try:
|
||||
project.delete_key_pair(key_name)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to delete key: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Key %s has been successfully deleted.' % \
|
||||
key_name)
|
||||
|
||||
return redirect('nova_keypairs', project_id)
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def download(request, project_id, key_name):
|
||||
# Ensure the project exists.
|
||||
shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
try:
|
||||
material = request.session.pop('key.%s' % key_name)
|
||||
except KeyError:
|
||||
return redirect('nova_keypairs', project_id)
|
||||
|
||||
response = http.HttpResponse(mimetype='application/binary')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.pem' % key_name
|
||||
response.write(material)
|
||||
|
||||
return response
|
107
django-nova/src/django_nova/views/projects.py
Normal file
107
django-nova/src/django_nova/views/projects.py
Normal file
@ -0,0 +1,107 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova projects.
|
||||
"""
|
||||
|
||||
from django import http
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import forms as nova_forms
|
||||
from django_nova.connection import get_nova_admin_connection
|
||||
from django_nova.exceptions import handle_nova_error
|
||||
from django_nova.shortcuts import get_project_or_404
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def detail(request, project_id):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
return render_to_response('django_nova/projects/index.html', {
|
||||
'project': project,
|
||||
'instance_count': project.get_instance_count(),
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def manage(request, project_id):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
if project.projectManagerId != request.user.username:
|
||||
return redirect('login')
|
||||
|
||||
nova = get_nova_admin_connection()
|
||||
members = nova.get_project_members(project_id)
|
||||
|
||||
for member in members:
|
||||
project_role = [str(role.role) for role in nova.get_user_roles(member.memberId, project_id)]
|
||||
global_role = [str(role.role) for role in nova.get_user_roles(member.memberId, project=False)]
|
||||
|
||||
member.project_roles = ", ".join(project_role)
|
||||
member.global_roles = ", ".join(global_role)
|
||||
|
||||
|
||||
return render_to_response('django_nova/projects/manage.html', {
|
||||
'project': project,
|
||||
'members': members,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def edit_user(request, project_id, username):
|
||||
nova = get_nova_admin_connection()
|
||||
project = get_project_or_404(request, project_id)
|
||||
user = nova.get_user(username)
|
||||
|
||||
if project.projectManagerId != request.user.username:
|
||||
return redirect('login')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = nova_forms.ProjectUserForm(project, user, request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
|
||||
return redirect('nova_project_manage', project_id)
|
||||
else:
|
||||
form = nova_forms.ProjectUserForm(project, user)
|
||||
|
||||
return render_to_response('django_nova/projects/edit_user.html', {
|
||||
'form' : form,
|
||||
'project': project,
|
||||
'user' : user,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def download_credentials(request, project_id):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
response = http.HttpResponse(mimetype='application/zip')
|
||||
response['Content-Disposition'] = \
|
||||
'attachment; filename=%s-%s-%s-x509.zip' % \
|
||||
(settings.SITE_NAME, project.projectname, request.user)
|
||||
response.write(project.get_zip())
|
||||
|
||||
return response
|
36
django-nova/src/django_nova/views/regions.py
Normal file
36
django-nova/src/django_nova/views/regions.py
Normal file
@ -0,0 +1,36 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova regions.
|
||||
"""
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect
|
||||
from django_nova.shortcuts import set_current_region
|
||||
|
||||
|
||||
@login_required
|
||||
def change(request):
|
||||
region = request.POST['region']
|
||||
redirect_url = request.POST['redirect_url']
|
||||
set_current_region(request, region)
|
||||
messages.success(request, 'You are now using the region "%s".' % region)
|
||||
|
||||
return redirect(redirect_url)
|
180
django-nova/src/django_nova/views/securitygroups.py
Normal file
180
django-nova/src/django_nova/views/securitygroups.py
Normal file
@ -0,0 +1,180 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova security groups.
|
||||
"""
|
||||
|
||||
from django import http
|
||||
from django import template
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import exceptions
|
||||
from django_nova import forms
|
||||
from django_nova.exceptions import handle_nova_error
|
||||
from django_nova.shortcuts import get_project_or_404
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def index(request, project_id):
|
||||
project = get_project_or_404(request, project_id)
|
||||
securitygroups = project.get_security_groups()
|
||||
|
||||
return render_to_response('django_nova/securitygroups/index.html', {
|
||||
'create_form': forms.CreateSecurityGroupForm(project),
|
||||
'project': project,
|
||||
'securitygroups': securitygroups,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def detail(request, project_id, group_name):
|
||||
project = get_project_or_404(request, project_id)
|
||||
securitygroup = project.get_security_group(group_name)
|
||||
|
||||
if not securitygroup:
|
||||
raise http.Http404
|
||||
|
||||
return render_to_response('django_nova/securitygroups/detail.html', {
|
||||
'authorize_form': forms.AuthorizeSecurityGroupRuleForm(),
|
||||
'project': project,
|
||||
'securitygroup': securitygroup,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def add(request, project_id):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.CreateSecurityGroupForm(project, request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
project.create_security_group(
|
||||
form.cleaned_data['name'],
|
||||
form.cleaned_data['description'])
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to create security group: %s' % e.message)
|
||||
else:
|
||||
messages.success(
|
||||
request,
|
||||
'Security Group %s has been succesfully created.' % \
|
||||
form.cleaned_data['name'])
|
||||
else:
|
||||
securitygroups = project.get_security_groups()
|
||||
|
||||
return render_to_response('django_nova/securitygroups/index.html', {
|
||||
'create_form': form,
|
||||
'project': project,
|
||||
'securitygroups': securitygroups,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
return redirect('nova_securitygroups', project_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def authorize(request, project_id, group_name):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.AuthorizeSecurityGroupRuleForm(request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
project.authorize_security_group(
|
||||
group_name = group_name,
|
||||
ip_protocol = form.cleaned_data['protocol'],
|
||||
from_port = form.cleaned_data['from_port'],
|
||||
to_port = form.cleaned_data['to_port'])
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to authorize: %s' % e.message)
|
||||
else:
|
||||
messages.success(
|
||||
request,
|
||||
'Security Group %s: Access to %s ports %d - %d'
|
||||
' has been authorized.' %
|
||||
(group_name,
|
||||
form.cleaned_data['protocol'],
|
||||
form.cleaned_data['from_port'],
|
||||
form.cleaned_data['to_port']))
|
||||
else:
|
||||
securitygroup = project.get_security_group(group_name)
|
||||
|
||||
if not securitygroup:
|
||||
raise http.Http404
|
||||
|
||||
return render_to_response('django_nova/securitygroups/detail.html', {
|
||||
'authorize_form': form,
|
||||
'project': project,
|
||||
'securitygroup': securitygroup,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
return redirect('nova_securitygroups_detail', project_id, group_name)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def revoke(request, project_id, group_name):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
project.revoke_security_group(
|
||||
group_name = group_name,
|
||||
ip_protocol = request.POST['protocol'],
|
||||
from_port = request.POST['from_port'],
|
||||
to_port = request.POST['to_port'])
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request, 'Unable to revoke: %s' % e.message)
|
||||
else:
|
||||
messages.success(
|
||||
request,
|
||||
'Security Group %s: Access to %s ports %s - %s '
|
||||
'has been revoked.' %
|
||||
(group_name,
|
||||
request.POST['protocol'],
|
||||
request.POST['from_port'],
|
||||
request.POST['to_port']))
|
||||
|
||||
return redirect('nova_securitygroups_detail', project_id, group_name)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def delete(request, project_id, group_name):
|
||||
project = get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
project.delete_security_group(name=group_name)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(
|
||||
request,
|
||||
'Unable to delete security group: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Security Group %s was successfully deleted.' %
|
||||
group_name)
|
||||
|
||||
return redirect('nova_securitygroups', project_id)
|
151
django-nova/src/django_nova/views/volumes.py
Normal file
151
django-nova/src/django_nova/views/volumes.py
Normal file
@ -0,0 +1,151 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Nova volumes.
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect, render_to_response
|
||||
from django_nova import exceptions
|
||||
from django_nova import forms
|
||||
from django_nova import shortcuts
|
||||
from django_nova.exceptions import handle_nova_error
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def index(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
volumes = project.get_volumes()
|
||||
|
||||
return render_to_response('django_nova/volumes/index.html', {
|
||||
'create_form': forms.CreateVolumeForm(),
|
||||
'attach_form': forms.AttachVolumeForm(project),
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'volumes': volumes,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def add(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.CreateVolumeForm(request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
volume = project.create_volume(form.cleaned_data['size'],
|
||||
form.cleaned_data['nickname'],
|
||||
form.cleaned_data['description'])
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to create volume: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Volume %s %s has been successfully created.' %
|
||||
(volume.id, volume.displayName))
|
||||
else:
|
||||
volumes = project.get_volumes()
|
||||
|
||||
return render_to_response('django_nova/volumes/index.html', {
|
||||
'create_form': form,
|
||||
'attach_form': forms.AttachVolumeForm(project),
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'volumes': volumes,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
return redirect('nova_volumes', project_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def delete(request, project_id, volume_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
project.delete_volume(volume_id)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to delete volume: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Volume %s has been successfully deleted.'
|
||||
% volume_id)
|
||||
|
||||
return redirect('nova_volumes', project_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def attach(request, project_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.AttachVolumeForm(project, request.POST)
|
||||
if form.is_valid():
|
||||
try:
|
||||
project.attach_volume(
|
||||
form.cleaned_data['volume'],
|
||||
form.cleaned_data['instance'],
|
||||
form.cleaned_data['device']
|
||||
)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to attach volume: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Volume %s has been successfully attached.' %
|
||||
form.cleaned_data['volume'])
|
||||
else:
|
||||
volumes = project.get_volumes()
|
||||
|
||||
return render_to_response('django_nova/volumes/index.html', {
|
||||
'create_form': forms.CreateVolumeForm(),
|
||||
'attach_form': form,
|
||||
'region': project.region,
|
||||
'project': project,
|
||||
'volumes': volumes,
|
||||
}, context_instance = template.RequestContext(request))
|
||||
|
||||
return redirect('nova_volumes', project_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@handle_nova_error
|
||||
def detach(request, project_id, volume_id):
|
||||
project = shortcuts.get_project_or_404(request, project_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
project.detach_volume(volume_id)
|
||||
except exceptions.NovaApiError, e:
|
||||
messages.error(request,
|
||||
'Unable to detach volume: %s' % e.message)
|
||||
else:
|
||||
messages.success(request,
|
||||
'Volume %s has been successfully detached.' %
|
||||
volume_id)
|
||||
|
||||
return redirect('nova_volumes', project_id)
|
49
openstack-dashboard/README
Normal file
49
openstack-dashboard/README
Normal file
@ -0,0 +1,49 @@
|
||||
OpenStack Dashboard
|
||||
-------------------
|
||||
|
||||
The OpenStack Dashboard is a reference implementation of a Django site that
|
||||
uses the Django-Nova project to provide web based interactions with the
|
||||
OpenStack Nova cloud controller.
|
||||
|
||||
For more information about the Django-Nova project, please visit:
|
||||
|
||||
http://launchpad.net/django-nova
|
||||
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
The first step is to obtain a local copy of the django-nova project:
|
||||
|
||||
$ mkdir django-nova
|
||||
$ cd django-nova
|
||||
$ bzr init-repo .
|
||||
$ bzr branch lp:django-nova/trunk
|
||||
|
||||
|
||||
Next we will create the virtualenv for local development. A tool is included to
|
||||
create one for you:
|
||||
|
||||
$ python tools/install_venv.py
|
||||
|
||||
|
||||
Now that the virtualenv is created, you need to configure your local
|
||||
environment. To do this, create a local_settings.py file in the local/
|
||||
directory. There is a local_settings.py.example file there that may be used
|
||||
as a template.
|
||||
|
||||
Finally, issue the django syncdb command:
|
||||
|
||||
$ tools/with_venv.sh dashboard/manage.py syncdb
|
||||
|
||||
If after you have specified the admin user the script appears to hang, it
|
||||
probably means the installation of Nova being referred to in local_settings.py
|
||||
is unavailable.
|
||||
|
||||
|
||||
If all is well you should now able to run the server locally:
|
||||
|
||||
$ tools/with_venv.sh dashboard/manage.py runserver
|
||||
|
||||
|
||||
|
0
openstack-dashboard/dashboard/__init__.py
Normal file
0
openstack-dashboard/dashboard/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user