diff options
author | Nathan Straz <nstraz@redhat.com> | 2009-02-02 18:36:40 -0500 |
---|---|---|
committer | Nathan Straz <nstraz@redhat.com> | 2009-02-02 18:36:40 -0500 |
commit | 7e1247b426ae86d7c660a9ca87f76d43773670f6 (patch) | |
tree | b91f593102a185a998bec50255e52ef0022de948 /st_web | |
download | steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.gz steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.xz steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.zip |
Import a sanitized version of Steel Toe, the provisioning system.
Diffstat (limited to 'st_web')
-rw-r--r-- | st_web/README | 34 | ||||
-rw-r--r-- | st_web/__init__.py | 0 | ||||
-rw-r--r-- | st_web/context_processors.py | 4 | ||||
-rw-r--r-- | st_web/st_config.py | 114 | ||||
-rw-r--r-- | st_web/templates/st_web.css | 109 | ||||
-rw-r--r-- | st_web/templates/st_web/confirm.html | 23 | ||||
-rw-r--r-- | st_web/templates/st_web/index.html | 14 | ||||
-rw-r--r-- | st_web/templates/st_web/install.html | 129 | ||||
-rw-r--r-- | st_web/templates/st_web/perform.html | 15 | ||||
-rw-r--r-- | st_web/templates/st_web/virtconfirm.html | 39 | ||||
-rw-r--r-- | st_web/templates/st_web/virthosts.html | 170 | ||||
-rw-r--r-- | st_web/templates/st_web/virtperform.html | 15 | ||||
-rw-r--r-- | st_web/templates/st_web/virtvolumes.html | 65 | ||||
-rw-r--r-- | st_web/templatetags/__init__.py | 0 | ||||
-rw-r--r-- | st_web/templatetags/ifin.py | 72 | ||||
-rw-r--r-- | st_web/urls.py | 11 | ||||
-rw-r--r-- | st_web/views.py | 262 | ||||
-rw-r--r-- | st_web/widgets.py | 71 |
18 files changed, 1147 insertions, 0 deletions
diff --git a/st_web/README b/st_web/README new file mode 100644 index 0000000..940ee42 --- /dev/null +++ b/st_web/README @@ -0,0 +1,34 @@ +Steel Toe Web Interface + +Requirements: + +This is a Django application which was developed using Django 0.96. +You will still need to generate a project which includes this +application. Make the following changes to your project. + +In settings.py: + +INSTALLED_APPS = ( + ... + 'st_web', +) + +In urls.py: +urlpatterns += patterns('', + (r'^steeltoe/', include('st_web.urls')), +) + + +It depends on the media context processor which was not part of +Django 0.96, but is available in development versions of Django. A +version of it is included in this application and should be added to the +template context processors by including this snippet in your +settings.py: + +from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS +TEMPLATE_CONTEXT_PROCESSORS += ( + 'st_web.context_processors.media', +) + +The stylesheet is included in the templates directory, you'll need to +copy it to settings.MEDIA_ROOT/st_web/st_web.css. diff --git a/st_web/__init__.py b/st_web/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/st_web/__init__.py diff --git a/st_web/context_processors.py b/st_web/context_processors.py new file mode 100644 index 0000000..482af15 --- /dev/null +++ b/st_web/context_processors.py @@ -0,0 +1,4 @@ +from django.conf import settings + +def media(request): + return {'MEDIA_URL': settings.MEDIA_URL} diff --git a/st_web/st_config.py b/st_web/st_config.py new file mode 100644 index 0000000..7869e10 --- /dev/null +++ b/st_web/st_config.py @@ -0,0 +1,114 @@ + +from xml.sax import make_parser, handler +from sets import Set +from os.path import getmtime + +class SteelToeXML(handler.ContentHandler): + + def __init__(self, filename="/etc/steeltoe/steeltoe.xml"): + self.filename = filename + self.reload() + + def reload(self): + self.mtimes = {self.filename: getmtime(self.filename)} + self.config = {'bootloader': {}} + self.hosts = [] + self.hostnames = Set() + self.groups = [] + self.groupnames = Set() + self.trees = [] + self.treenames = Set() + self.meta = {} # tree metadata index + self._context = [] + self._curname = "" + parser = make_parser() + parser.setContentHandler(self) + parser.parse(self.filename) + + def refresh(self): + reload = False + for (fn, mtime) in self.mtimes.items(): + if mtime != getmtime(fn): + reload = True + if reload: + self.reload() + + def get_group(self, name): + return [x for x in self.groups if x['name'] == name][0] + + def get_host(self, name): + return [x for x in self.hosts if x['name'] == name][0] + + def characters(self, content): + self._text = self._text + content + + def startElement(self, name, attrs): + if name in ['config', 'tree', 'host', 'bootloader', 'kickstart', 'meta']: + self._context.append(name) + if name in ['group', 'tree', 'host']: + self._curname = attrs['name'] + if name == 'repo': + if not self._curtree.has_key(name): + self._curtree[name] = [attrs['name']] + else: + self._curtree[name].append(attrs['name']) + if name == 'host': + self._curhost = {'name': self._curname} + self.hostnames.add(self._curname) + if name == 'tree': + self._curtree = {'name': self._curname, 'meta': {}} + self.treenames.add(self._curname) + if name == 'group': + self.groupnames.add(self._curname) + # Hack to handle simple XIncludes + if name == 'xi:include': + href = attrs['href'] + self.mtimes[href] = getmtime(href) + subp = make_parser() + subp.setContentHandler(self) + subp.parse(href) + self._text = "" + + def _parent(self): + if len(self._context): + return self._context[-1] + else: + None + + def endElement(self, name): + self._text = self._text.strip() + if self._parent() == name: + self._context.pop() + if self._parent() == 'config': + self.config[name] = self._text + elif self._parent() == 'bootloader': + self.config['bootloader'][name] = self._text + elif name == 'group': + self.groups.append({ 'name': self._curname, + 'members': self._text.split()}) + self._curname = None + elif self._parent() == 'kickstart' or name == 'kickstart': + # Ignore the kickstart sections for now + pass + elif self._parent() == 'host': + self._curhost[name] = self._text + elif name == 'host': + self.hosts.append(self._curhost) + self._curname = None + elif self._parent() == 'meta': + self._curtree['meta'][name] = self._text + if not self.meta.has_key(name): + self.meta[name] = {self._text: Set([self._curname])} + elif not self.meta[name].has_key(self._text): + self.meta[name][self._text] = Set([self._curname]) + else: + self.meta[name][self._text].add(self._curname) + elif self._parent() == 'tree': + if name == 'path': + self._curtree[name] = self._text + elif name == 'tree': + self.trees.append(self._curtree) + self._curname = None + + +steeltoe = SteelToeXML() diff --git a/st_web/templates/st_web.css b/st_web/templates/st_web.css new file mode 100644 index 0000000..b04c130 --- /dev/null +++ b/st_web/templates/st_web.css @@ -0,0 +1,109 @@ +h1 { + text-align: center; +} + +h2 { + font-size: large; + font-weight: normal; +} +.yui-navset { + margin: 0 0 1em 5%; + width: 90%; +} + +.yui-navset .yui-nav li { + padding: 1px 0 0; + background: #800000; +} + +.yui-navset .yui-nav .selected { +} + +.yui-navset .yui-nav a { + padding: 0 1em; + color: #000; + text-decoration: none; +} + +.yui-navset .yui-nav .selected a, +.yui-navset .yui-nav .selected a:focus, +.yui-navset .yui-nav .selected a:hover { + background: #C00; + color: #FFF; +} + +.yui-navset .yui-nav a:hover, +.yui-navset .yui-nav a:focus { + background: #cc0000; + color: #FFF; + outline: 0; +} + +.yui-content { + height: 20em; + overflow: auto; + border-left: 2px solid #C00; + border-right: 2px solid #C00; + border-top: 5px solid #C00; + border-bottom: 5px solid #C00; + padding: 0.25em 0; + background: white; +} +.yui-content p { + margin: 1em 1em; +} + +.yui-content table { + margin: 0; + padding: 0; + width: 100%; + border-collapse: collapse; +} + +.yui-content table td { + padding: 1px 1em; + border-left: 1px dotted; + border-right: 1px dotted; +} +.yui-content table tr.heading { + border-bottom: 2px solid #000; +} + +.yui-content table tr.odd { + background: #FFF; +} + +.yui-content table tr.even { + background: #FED; +} + +.yui-content table td.input { + text-align: center; + width: 1em; +} + +ul.errorlist { + margin: 0 0 1em 5%; + width: 75%; + background-color: #C00; + border: 2px solid #C00; +} +.errorlist li { + display: block; + background: #FDD; + padding: 0.25em 1em; +} + +input { + background: #FED; + border: 1px solid #999; + color: black; +} + +pre { + width: 75%; + margin: 0 0 1em 5%; + padding: 1em; + background: #FED; + border: 2px solid #C00; +} diff --git a/st_web/templates/st_web/confirm.html b/st_web/templates/st_web/confirm.html new file mode 100644 index 0000000..a780a75 --- /dev/null +++ b/st_web/templates/st_web/confirm.html @@ -0,0 +1,23 @@ +<html> +<head> +<title>Steel Toe Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +</head> +<body> +<h1>Steel Toe Install Confirmation</h1> +<h2>Please verify that you want to install:</h2> +<ul><li>{{ tree }}</li></ul> +<h2>on the following hosts:</h2> +<ul> +{% for host in hosts %} +<li>{{ host }}</li> +{% endfor %} +</ul> +<form action="." method="post"> +{{ form.tree.as_hidden }} +{{ form.hosts.as_hidden }} +<input type="submit" name="install" value="Confirm"/> +<input type="submit" name="install" value="Cancel"/> +</form> +</body> +</html> diff --git a/st_web/templates/st_web/index.html b/st_web/templates/st_web/index.html new file mode 100644 index 0000000..cb2ef9f --- /dev/null +++ b/st_web/templates/st_web/index.html @@ -0,0 +1,14 @@ +<html> +<head> +<title>Steel Toe</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +</head> +<body> +<h1>Steel Toe</h1> +<h2>Available Actions</h2> +<ul> +<li><a href="{% url st_web.views.install %}">Install</a></li> +<li><a href="{% url st_web.views.virtinstall %}">Virtual Machine Install</a></li> +</ul> +</body> +</html> diff --git a/st_web/templates/st_web/install.html b/st_web/templates/st_web/install.html new file mode 100644 index 0000000..e5870b0 --- /dev/null +++ b/st_web/templates/st_web/install.html @@ -0,0 +1,129 @@ +{% load ifin %} +<html> +<head> +<title>Steel Toe Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" /> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script> +</head> +<body> +<h1>Steel Toe Install</h1> +<form action="{% url st_web.views.install %}" method="post"> + +{% if form.errors %} +{{ form.non_field_errors }} +{% endif %} + +<h2>Select Tree to install</h2> +{{ form.tree.errors }} +<div id="treeselect" class="yui-navset"> + <ul class="yui-nav"> + <li class="selected"><a href="#tall">All</a></li> + <li><a href="#trecent">Recently Used</a></li> + <li><a href="#tinstalled">Installed</a></li> + </ul> + <div class="yui-content"> + <div id="tall"> + {% regroup steeltoe.trees|dictsort:"name" by name as trees %} + <table> + <tr class="heading"><th>Select</th><th>Tree Name</th><th>Kernel</th><th>Links</th></tr> + {% for tree in trees %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="radio" name="tree" value="{{ tree.grouper }}" {% ifequal tree.grouper form.tree.data %}checked="checked"{% endifequal %}></td> + <td>{{ tree.grouper }}</td> + <td>{{ tree.list.0.meta.kernel }}</td> + <td>{% for limb in tree.list %} + <a href="http://{{ steeltoe.config.treeserver }}{{ limb.path }}">{{ limb.meta.arch }}</a> + {% endfor %} + </td> + </tr> + {% endfor %} + </table> + + </div> + <div id="trecent"> + {% if request.session.trees %} + <table> + <tr class="heading"><th>Select</th><th>Tree Name</th><th>Times installed</th></tr> + {% for tree in request.session.trees %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="radio" name="tree" value="{{ tree.name }}" {% ifequal tree.name form.tree.data %}checked="checked"{% endifequal %}></td> + <td>{{ tree.name }}</td> + <td>{{ tree.count }}</td> + </tr> + {% endfor %} + </table> + {% else %} + <p>A list of recently installed trees</p> + {% endif %} + </div> + <div id="tinstalled"> + <p>A list of all trees installed and where</p> + </div> + </div> +</div> + +<h2>Select Hosts on which to install</h2> +{{ form.hosts.errors }} +<div id="hostselect"> + <ul class="yui-nav"> + <li class="selected"><a href="#hall">All</a></li> + <li><a href="#hgroups">Groups</a></li> + <li><a href="#hrecent">Your Systems</a></li> + </ul> + <div class="yui-content"> + <div id="hall"> + <table> + <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr> + {% for host in steeltoe.hosts|dictsort:"name" %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name form.hosts.data %}checked="checked"{% endifin %}></td> + <td>{{ host.name }}</td> + <td>{{ host.arch }}</td> + </tr> + {% endfor %} + </table> + </div> + <div id="hgroups"> + <table> + <tr class="heading"><th>Select</th><th>Group name</th><th>Group Members</th></tr> + {% for group in steeltoe.groups|dictsort:"name" %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="checkbox" name="hosts" value="{{ group.name }}" {% ifin group.name form.hosts.data %}checked="checked"{% endifin %}></td> + <td>{{ group.name }}</td> + <td>{% for m in group.members %} {{ m }} {% endfor %}</td> + </tr> + {% endfor %} + </table> + </div> + <div id="hrecent"> + {% if request.session.hosts %} + <table> + <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr> + {% for host in request.session.hosts %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name form.hosts.data %}checked="checked"{% endifin %}></td> + <td>{{ host.name }}</td> + <td>{{ host.count }}</td> + </tr> + {% endfor %} + </table> + {% else %} + <p>A list of hosts which you have installed on</p> + {% endif %} + </div> + </div> +</div> + +<script> +var tv = new YAHOO.widget.TabView('treeselect'); +var hv = new YAHOO.widget.TabView('hostselect'); +</script> + +<input type="reset" value="Clear Selections"> +<input type="submit" name="install" value="Install"> +</form> +</body> +</html> diff --git a/st_web/templates/st_web/perform.html b/st_web/templates/st_web/perform.html new file mode 100644 index 0000000..2d1b195 --- /dev/null +++ b/st_web/templates/st_web/perform.html @@ -0,0 +1,15 @@ +<html> +<head> +<title>Steel Toe Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +</head> +<body> +<h1>Steel Toe Install Output</h1> +<h2>Output from Steel Toe install command.</h2> +<pre> +{{ output }} +</pre> + +<form action="." method="get"><input type="submit" value="Do Another Install"/></form> +</body> +</html> diff --git a/st_web/templates/st_web/virtconfirm.html b/st_web/templates/st_web/virtconfirm.html new file mode 100644 index 0000000..c03dedc --- /dev/null +++ b/st_web/templates/st_web/virtconfirm.html @@ -0,0 +1,39 @@ +<html> +<head> +<title>Steel Toe Virtual Machine Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +</head> +<body> +<h1>Steel Toe Virtual Machine Install Confirmation</h1> + +<h2>Please verify the following operation:</h2> +<p>You want to install <b>{{ tree }}</b>, using <b>{{ dom0 }}</b> as the installation host, on:</p> + +<ul> +{% for host in hosts %} +<li>{{ host.name }} with root device /dev/{{ host.vg }}/{{ host.lv }} ({{ host.size }}),</li> +{% endfor %} +</ul> +{% if shared %} +<p>and shared device{{ shared|length|pluralize }}</p> +<ul> +{% for v in shared %} +<li>/dev/{{ v.vg }}/{{ v.lv }} ({{ v.size }})</li> +{% endfor %} +</ul> +{% else %} +<p>and no shared devices.</p> +{% endif %} +<form action="." method="post"> +{% for f in hostform %} +{{ f.as_hidden }} +{% endfor %} +{% for f in volform %} +{{ f.as_hidden }} +{% endfor %} +<input type="hidden" name="step" value="confirm"/> +<input type="submit" name="install" value="Confirm"/> +<input type="submit" name="install" value="Cancel"/> +</form> +</body> +</html> diff --git a/st_web/templates/st_web/virthosts.html b/st_web/templates/st_web/virthosts.html new file mode 100644 index 0000000..27f844c --- /dev/null +++ b/st_web/templates/st_web/virthosts.html @@ -0,0 +1,170 @@ +{% load ifin %} +<html> +<head> +<title>Steel Toe Virtual Machine Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" /> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script> +</head> +<body> +<h1>Steel Toe Virtual Machine Install</h1> +<form action="{% url st_web.views.virtinstall %}" method="post"> + +{% if hostform.errors %} +{{ hostform.non_field_errors }} +{% endif %} + +<h2>Select Tree to install</h2> +{{ hostform.tree.errors }} +<div id="treeselect" class="yui-navset"> + <ul class="yui-nav"> + <li class="selected"><a href="#tall">All</a></li> + <li><a href="#trecent">Recently Used</a></li> + <li><a href="#tinstalled">Installed</a></li> + </ul> + <div class="yui-content"> + <div id="tall"> + {% regroup steeltoe.trees|dictsort:"name" by name as trees %} + <table> + <tr class="heading"><th>Select</th><th>Tree Name</th><th>Kernel</th><th>Links</th></tr> + {% for tree in trees %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="radio" name="tree" value="{{ tree.grouper }}" {% ifequal tree.grouper hostform.tree.data %}checked="checked"{% endifequal %}></td> + <td>{{ tree.grouper }}</td> + <td>{{ tree.list.0.meta.kernel }}</td> + <td>{% for limb in tree.list %} + <a href="http://{{ steeltoe.config.treeserver }}{{ limb.path }}">{{ limb.meta.arch }}</a> + {% endfor %} + </td> + </tr> + {% endfor %} + </table> + + </div> + <div id="trecent"> + {% if request.session.trees %} + <table> + <tr class="heading"><th>Select</th><th>Tree Name</th><th>Times installed</th></tr> + {% for tree in request.session.trees %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="radio" name="tree" value="{{ tree.name }}" {% ifequal tree.name hostform.tree.data %}checked="checked"{% endifequal %}></td> + <td>{{ tree.name }}</td> + <td>{{ tree.count }}</td> + </tr> + {% endfor %} + </table> + {% else %} + <p>A list of recently installed trees</p> + {% endif %} + </div> + <div id="tinstalled"> + <p>A list of all trees installed and where</p> + </div> + </div> +</div> + +<h2>Select a Physical Host on which to install</h2> +{{ hostform.dom0.errors }} +<div id="dom0select"> + <ul class="yui-nav"> + <li class="selected"><a href="#hall">All</a></li> + <li><a href="#hrecent">Your Systems</a></li> + </ul> + <div class="yui-content"> + <div id="hall"> + <table> + <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr> + {% for host in steeltoe.hosts|dictsort:"name" %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="radio" name="dom0" value="{{ host.name }}" {% ifin host.name hostform.dom0.data %}checked="checked"{% endifin %}></td> + <td>{{ host.name }}</td> + <td>{{ host.arch }}</td> + </tr> + {% endfor %} + </table> + </div> + <div id="hrecent"> + {% if request.session.hosts %} + <table> + <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr> + {% for host in request.session.hosts %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="radio" name="dom0" value="{{ host.name }}" {% ifin host.name hostform.dom0.data %}checked="checked"{% endifin %}></td> + <td>{{ host.name }}</td> + <td>{{ host.count }}</td> + </tr> + {% endfor %} + </table> + {% else %} + <p>A list of hosts which you have installed on</p> + {% endif %} + </div> + </div> +</div> + +<h2>Select Virtual Hosts on which to install</h2> +{{ hostform.hosts.errors }} +<div id="hostselect"> + <ul class="yui-nav"> + <li class="selected"><a href="#hall">All</a></li> + <li><a href="#hgroups">Groups</a></li> + <li><a href="#hrecent">Your Systems</a></li> + </ul> + <div class="yui-content"> + <div id="hall"> + <table> + <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr> + {% for host in steeltoe.hosts|dictsort:"name" %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name hostform.hosts.data %}checked="checked"{% endifin %}></td> + <td>{{ host.name }}</td> + <td>{{ host.arch }}</td> + </tr> + {% endfor %} + </table> + </div> + <div id="hgroups"> + <table> + <tr class="heading"><th>Select</th><th>Group name</th><th>Group Members</th></tr> + {% for group in steeltoe.groups|dictsort:"name" %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="checkbox" name="hosts" value="{{ group.name }}" {% ifin group.name hostform.hosts.data %}checked="checked"{% endifin %}></td> + <td>{{ group.name }}</td> + <td>{% for m in group.members %} {{ m }} {% endfor %}</td> + </tr> + {% endfor %} + </table> + </div> + <div id="hrecent"> + {% if request.session.hosts %} + <table> + <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr> + {% for host in request.session.hosts %} + <tr class="{% cycle odd,even %}"> + <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name hostform.hosts.data %}checked="checked"{% endifin %}></td> + <td>{{ host.name }}</td> + <td>{{ host.count }}</td> + </tr> + {% endfor %} + </table> + {% else %} + <p>A list of hosts which you have installed on</p> + {% endif %} + </div> + </div> +</div> + +<script> +var tv = new YAHOO.widget.TabView('treeselect'); +var dv = new YAHOO.widget.TabView('dom0select'); +var hv = new YAHOO.widget.TabView('hostselect'); +</script> + +<input type="hidden" name="step" value="hosts"/> +<input type="reset" value="Clear Selections"> +<input type="submit" name="install" value="Next"> +</form> +</body> +</html> diff --git a/st_web/templates/st_web/virtperform.html b/st_web/templates/st_web/virtperform.html new file mode 100644 index 0000000..5de5d60 --- /dev/null +++ b/st_web/templates/st_web/virtperform.html @@ -0,0 +1,15 @@ +<html> +<head> +<title>Steel Toe Virtual Machine Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +</head> +<body> +<h1>Steel Toe Virtual Machine Install Output</h1> +<h2>Output from Steel Toe virtinstall command.</h2> +<pre> +{{ output }} +</pre> + +<form action="." method="get"><input type="submit" value="Do Another Install"/></form> +</body> +</html> diff --git a/st_web/templates/st_web/virtvolumes.html b/st_web/templates/st_web/virtvolumes.html new file mode 100644 index 0000000..3bf3571 --- /dev/null +++ b/st_web/templates/st_web/virtvolumes.html @@ -0,0 +1,65 @@ +{% load ifin %} +<html> +<head> +<title>Steel Toe Install</title> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/> +<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" /> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script> +<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script> +</head> +<body> +<h1>Steel Toe Virtual Machine Install</h1> +<form action="{% url st_web.views.virtinstall %}" method="post"> + +{% if volform.fields %} + +<h2>Match volumes with virtual machines</h2> + +{% if volform.errors %} +{{ volform.non_field_errors }} +{% endif %} + +<div class="yui-navset"> +<div class="yui-content"> +<table> +<tr class="heading"> +<th>Logical Volume</th><th>Volume Group</th><th>Size</th> +{% for choice in volform.fields.values.0.choices %}<th>{{ choice.1 }}</th>{% endfor %} +</tr> +{% for bf in volform %} +<tr class="{% cycle odd,even %}"><td>{{ bf.field.lv }}</td><td>{{ bf.field.vg }}</td><td>{{ bf.field.size }}</td> + {{ bf }} +{% if bf.errors %}<td>{{ bf.errors }}</td>{% endif %} +</tr> +{% endfor %} +</table> +</div> +</div> + +{% for f in hostform %} +{{ f.as_hidden }} +{% endfor %} + +<input type="hidden" name="step" value="vols"/> +<input type="submit" name="install" value="Cancel"> +<input type="reset" value="Clear Selections"> +<input type="submit" name="install" value="Install"> +{% else %} +<h2>No volumes found</h2> +There are many reasons we couldn't find volumes to use. +<ul> +<li>The remote shell could not connect to the dom0.</li> +<li>All logical volumes are currently in use.</li> +</ul> +{% for f in hostform %} +{{ f.as_hidden }} +{% endfor %} + +<input type="hidden" name="step" value="vols"/> +<input type="submit" name="install" value="Cancel"> + +{% endif %} +</form> +</body> +</html> diff --git a/st_web/templatetags/__init__.py b/st_web/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/st_web/templatetags/__init__.py diff --git a/st_web/templatetags/ifin.py b/st_web/templatetags/ifin.py new file mode 100644 index 0000000..d514985 --- /dev/null +++ b/st_web/templatetags/ifin.py @@ -0,0 +1,72 @@ +from django.template import resolve_variable +from django.template import Node, NodeList +from django.template import VariableDoesNotExist +from django.template import Library + +register = Library() + +class IfInNode(Node): + def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): + self.var1, self.var2 = var1, var2 + self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false + self.negate = negate + + def __repr__(self): + return "<IfInNode>" + + def render(self, context): + try: + val1 = resolve_variable(self.var1, context) + except VariableDoesNotExist: + val1 = None + try: + val2 = resolve_variable(self.var2, context) + except VariableDoesNotExist: + val2 = [] + try: + if (self.negate and not val1 in val2) or (not self.negate and val1 in val2): + return self.nodelist_true.render(context) + except TypeError: + return "" + return self.nodelist_false.render(context) + +def do_ifin(parser, token, negate): + bits = list(token.split_contents()) + if len(bits) != 3: + raise TemplateSyntaxError, "%r takes two arguments" % bits[0] + end_tag = 'end' + bits[0] + nodelist_true = parser.parse(('else', end_tag)) + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse((end_tag,)) + parser.delete_first_token() + else: + nodelist_false = NodeList() + return IfInNode(bits[1], bits[2], nodelist_true, nodelist_false, negate) + +#@register.tag +def ifin(parser, token): + """ + Output the contents of the block if the first argument is in the second argument. + + Examples:: + + {% ifin user.id comment.user_id %} + ... + {% endifin %} + + {% ifnotin user.id comment.user_id %} + ... + {% else %} + ... + {% endifnotin %} + """ + return do_ifin(parser, token, False) +ifin = register.tag(ifin) + +#@register.tag +def ifnotin(parser, token): + """Output the contents of the block if the first argument is not in the second argument. See ifin.""" + return do_ifin(parser, token, True) + +ifnotin = register.tag(ifnotin) diff --git a/st_web/urls.py b/st_web/urls.py new file mode 100644 index 0000000..ee0152a --- /dev/null +++ b/st_web/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('', + # Example: + (r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'st_web/index.html'}), + (r'^install/$', 'st_web.views.install'), + (r'^virtinstall/$', 'st_web.views.virtinstall'), + + # Uncomment this for admin: +# (r'^admin/', include('django.contrib.admin.urls')), +) diff --git a/st_web/views.py b/st_web/views.py new file mode 100644 index 0000000..cfd3124 --- /dev/null +++ b/st_web/views.py @@ -0,0 +1,262 @@ +# Main views for the Steeltoe Web Interface. + +from sets import Set +from commands import getoutput + +from django.http import * +from django.template import RequestContext +from django.shortcuts import render_to_response +from django import newforms as forms +from django.newforms.util import ErrorList +from django.utils.datastructures import SortedDict + +from st_web.st_config import steeltoe +from st_web.widgets import VolSelect + +def steeltoe_install_form(): + "Generate a validatable form with choices from the steeltoe config" + def _get_trees(): + return tuple([(x, x) for x in steeltoe.treenames]) + def _get_hosts(): + return tuple([(x, x) for x in steeltoe.hostnames | steeltoe.groupnames]) + + class SteeltoeInstallForm(forms.Form): + tree = forms.ChoiceField(choices = _get_trees(), widget=forms.RadioSelect) + hosts = forms.MultipleChoiceField(choices = _get_hosts(), widget=forms.CheckboxSelectMultiple) + + def clean(self): + "Test that the arch of the hosts matches the archs of the tree" + errors = ErrorList() + t = self.clean_data.get('tree', None) + if t is None: + return self.clean_data + tree_archs = [x['meta']['arch'] for x in steeltoe.trees if x['name'] == t] + # Normalize the list into hostnames + hostlist = Set() + for host in self.clean_data.get('hosts', []): + if host in steeltoe.groupnames: + hostlist |= Set(steeltoe.get_group(host)['members']) + elif host in steeltoe.hostnames: + hostlist.add(host) + else: + errors.append("Host %s not valid" % host) + # Convert the list to host entries + hostlist = list(hostlist) + hostlist.sort() + self.clean_data['hosts'] = hostlist + hostlist = [steeltoe.get_host(x) for x in hostlist] + # Do the tests + for host in hostlist: + if not host['arch'] in tree_archs: + errors.append("Tree %s is not available for %s which host %s requires" % (t, host['arch'], host['name'])) + if errors: + raise forms.ValidationError(errors) + return self.clean_data + + return SteeltoeInstallForm + +def install(request): + if request.method == 'POST': + form = steeltoe_install_form()(request.POST) + if form.is_valid(): + cmd = request.POST.get('install', 'Install') + if cmd == 'Install': + return render_to_response("st_web/confirm.html", dict(form.clean_data, form=form), + context_instance=RequestContext(request)) + elif cmd == 'Confirm': + t = form.clean_data['tree'] + h = ' '.join(form.clean_data['hosts']) + update_session(request.session, t, form.clean_data['hosts']) + output = getoutput("/usr/bin/steeltoe install -r %s %s" % (t, h)) + return render_to_response("st_web/perform.html", {'output': output}, + context_instance=RequestContext(request)) + else: + steeltoe.refresh() + form = steeltoe_install_form()() + return render_to_response("st_web/install.html", { 'steeltoe': steeltoe , 'form': form }, + context_instance=RequestContext(request)) + +def steeltoe_virthost_form(): + "Generate a validatable form with choices from the steeltoe config" + def _get_trees(): + return tuple([(x, x) for x in steeltoe.treenames]) + def _get_dom0s(): + return tuple([(x, x) for x in steeltoe.hostnames]) + def _get_hosts(): + return tuple([(x, x) for x in steeltoe.hostnames | steeltoe.groupnames]) + + class SteeltoeVirtHostForm(forms.Form): + tree = forms.ChoiceField(choices = _get_trees(), widget=forms.RadioSelect) + dom0 = forms.ChoiceField(choices = _get_dom0s(), widget=forms.RadioSelect) + hosts = forms.MultipleChoiceField(choices = _get_hosts(), widget=forms.CheckboxSelectMultiple) + + def clean(self): + "Test that the arch of the hosts matches the archs of the tree" + errors = ErrorList() + t = self.clean_data.get('tree', None) + if t is None: + return self.clean_data + tree_archs = [x['meta']['arch'] for x in steeltoe.trees if x['name'] == t] + d0 = self.clean_data.get('dom0', None) + if d0 is None: + return self.clean_data + # Normalize the list into hostnames + hostlist = Set() + for host in self.clean_data.get('hosts', []): + if host in steeltoe.groupnames: + hostlist |= Set(steeltoe.get_group(host)['members']) + elif host in steeltoe.hostnames: + hostlist.add(host) + else: + errors.append("Host %s not valid" % host) + # Convert the list to host entries + hostlist = list(hostlist) + hostlist.sort() + if d0 in hostlist: + errors.append("Can not install on %s from %s" % (d0, d0)) + self.clean_data['hosts'] = hostlist + hostlist = [steeltoe.get_host(x) for x in hostlist] + # Do the tests + for host in hostlist: + if not host['arch'] in tree_archs: + errors.append("Tree %s is not available for %s which host %s requires" % (t, host['arch'], host['name'])) + if errors: + raise forms.ValidationError(errors) + return self.clean_data + + return SteeltoeVirtHostForm + + +def steeltoe_virtvols_form(dom0, domUs): + "Generate a validatable form for the volumes which we're going to install the hosts to" + def _get_vols(host): + rsh = steeltoe.config.get("remoteshell", "/bin/false") + output = getoutput("%s -l root %s /usr/sbin/lvs --noheadings -o lv_name,vg_name,lv_size,lv_attr 2>/dev/null" % (rsh, host)) + vols = [] + for l in output.split('\n'): + if len(l.split()) != 4: + continue + d = dict(zip(('lv', 'vg', 'size', 'attr'), l.split())) + # skip over open volumes + if d['attr'][5] == 'o': + continue + vols.append(d) + return vols + + def clean(form): + errors = ErrorList() + # Check that each host has one and only one root volume + hostroots = dict([(h, []) for h in form.hosts]) + shared = [] + for vol in form.fields: + host = form.clean_data[vol] + if host == 'none': + continue + elif host == 'shared': + shared.append(vol) + continue + elif not hostroots.has_key(host): + errors.append("Unexpected host " + host) + else: + hostroots[host].append(vol) + for host, roots in hostroots.items(): + if len(roots) > 1: + errors.append("Too many roots assigned to %s" % host) + elif len(roots) == 0: + errors.append("Root not assigned to %s" % host) + form.clean_data['shared'] = [{'lv': form.fields[v].lv, 'vg': form.fields[v].vg, 'size': form.fields[v].size} for v in shared] + if errors: + raise forms.ValidationError(errors) + # Add an easily usable dict to look hosts' root vol + form.clean_data['hosts'] = [({'name': k, 'lv': form.fields[v[0]].lv, 'vg': form.fields[v[0]].vg, 'size': form.fields[v[0]].size}) for k, v in hostroots.items()] + return form.clean_data + + vols = _get_vols(dom0) + choices = tuple([(h, 'Root for %s' % h) for h in domUs]) + (('shared', 'Shared Volume'), ('none', 'Not Used')) + + base_fields = SortedDict() + for vol in vols: + f = forms.ChoiceField(choices, widget=VolSelect, initial='none') + for k, v in vol.items(): + setattr(f, k, v) + base_fields["%(vg)s_%(lv)s" % vol] = f + + return type('SteeltoeVirtVolForm', (forms.BaseForm,), + {'base_fields': base_fields, + 'hosts': domUs, + 'dom0': dom0, + 'vols': vols, + 'clean': clean, + }) + + +def virtinstall(request): + if request.method == 'POST': + # Any POST request should have a host form to validate. + hostform = steeltoe_virthost_form()(request.POST) + if hostform.is_valid(): + step = request.POST.get('step', 'hosts') + cmd = request.POST.get('install', 'Next') + dom0 = hostform.clean_data['dom0'] + hosts = hostform.clean_data['hosts'] + if step == 'vols' and cmd == 'Cancel': + return render_to_response("st_web/virthosts.html", + { 'steeltoe': steeltoe , 'hostform': hostform }, + context_instance=RequestContext(request)) + + if step in ('vols', 'confirm'): + volform = steeltoe_virtvols_form(dom0, hosts)(request.POST) + if volform.is_valid(): + if step == 'vols' and cmd == 'Install': + data = dict(hostform.clean_data) + data.update(volform.clean_data) + data['hostform'] = hostform + data['volform'] = volform + return render_to_response("st_web/virtconfirm.html", data, + context_instance=RequestContext(request)) + elif step == 'confirm' and cmd == 'Confirm': + t = hostform.clean_data['tree'] + update_session(request.session, t, hostform.clean_data['hosts']) + output = "" + for h in volform.clean_data['hosts']: + cmdargs = ("/usr/bin/steeltoe", "virtinstall") + cmdargs += (t, h['name'], dom0) + cmdargs += "/dev/%(vg)s/%(lv)s" % h, + cmdargs += tuple(["/dev/%(vg)s/%(lv)s" % v for v in volform.clean_data['shared']]) + output += getoutput(' '.join(cmdargs)) + return render_to_response("st_web/virtperform.html", {'output': output}, + context_instance=RequestContext(request)) + # else: fall through to redisplay the volumes form + # else: fall through to redisplay the volumes form with errors + else: + volform = steeltoe_virtvols_form(dom0, hosts)() + # FIXME: What about back button for volumes -> hosts + return render_to_response("st_web/virtvolumes.html", + dict(hostform=hostform, volform=volform), + context_instance=RequestContext(request)) + # else: host form is not valid, fall through to the first page + else: + steeltoe.refresh() + hostform = steeltoe_virthost_form()() + return render_to_response("st_web/virthosts.html", { 'steeltoe': steeltoe , 'hostform': hostform }, + context_instance=RequestContext(request)) + + +def update_session(session, newtree, newhosts): + trees = session.get('trees', []) + if newtree in Set([x['name'] for x in trees]): + tree = [x for x in trees if x['name'] == newtree][0] + tree['count'] += 1 + else: + trees.append({'name': newtree, 'count': 1}) + trees.sort(lambda x, y: cmp(y['count'], x['count']) or cmp(x['name'], y['name'])) + session['trees'] = [x for x in trees if x['name'] in steeltoe.treenames] + hosts = session.get('hosts', []) + for newhost in newhosts: + if newhost in Set([x['name'] for x in hosts]): + host = [x for x in hosts if x['name'] == newhost][0] + host['count'] += 1 + else: + hosts.append({'name': newhost, 'count': 1}) + hosts.sort(lambda x, y: cmp(y['count'], x['count']) or cmp(x['name'], y['name'])) + session['hosts'] = [x for x in hosts if x['name'] in steeltoe.hostnames] diff --git a/st_web/widgets.py b/st_web/widgets.py new file mode 100644 index 0000000..c92424f --- /dev/null +++ b/st_web/widgets.py @@ -0,0 +1,71 @@ +from django import newforms as forms +from django.newforms.util import flatatt, StrAndUnicode, smart_unicode +from itertools import chain + +__all__ = ('VolSelect') + +class RadioInput(StrAndUnicode): + "An object used by RadioFieldRenderer that represents a single <input type='radio'>." + def __init__(self, name, value, attrs, choice, index): + self.name, self.value = name, value + self.attrs = attrs + self.choice_value = smart_unicode(choice[0]) + self.choice_label = smart_unicode(choice[1]) + self.index = index + + def __unicode__(self): + return u'<label>%s %s</label>' % (self.tag(), self.choice_label) + + def is_checked(self): + return self.value == self.choice_value + + def tag(self): + if self.attrs.has_key('id'): + self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index) + final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) + if self.is_checked(): + final_attrs['checked'] = 'checked' + return u'<input%s />' % flatatt(final_attrs) + +class RadioFieldRenderer(StrAndUnicode): + "An object used by RadioSelect to enable customization of radio widgets." + def __init__(self, name, value, attrs, choices): + self.name, self.value, self.attrs = name, value, attrs + self.choices = choices + + def __iter__(self): + for i, choice in enumerate(self.choices): + yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) + + def __getitem__(self, idx): + choice = self.choices[idx] # Let the IndexError propogate + return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx) + + def __unicode__(self): + "Outputs a <ul> for this set of radio fields." + return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self]) + +class VolInput(RadioInput): + "An object used by RadioFieldRenderer that represents a single <input type='radio'>." + def __unicode__(self): + return u'<td class="input">%s</td>' % self.tag() + +class VolFieldRenderer(RadioFieldRenderer): + def __iter__(self): + for i, choice in enumerate(self.choices): + yield VolInput(self.name, self.value, self.attrs.copy(), choice, i) + def __getitem__(self, idx): + choice = self.choices[idx] # Let the IndexError propogate + return VolInput(self.name, self.value, self.attrs.copy(), choice, idx) + def __unicode__(self): + "Outputs the set of radio fields." + return u'\n'.join([u'%s' % w for w in self]) + + +class VolSelect(forms.RadioSelect): + def render(self, name, value, attrs=None, choices=()): + "Returns a VolFieldRenderer instance rather than a Unicode string." + if value is None: value = '' + str_value = smart_unicode(value) + attrs = attrs or {} + return VolFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices))) |