diff --git a/.#Makefile b/.#Makefile new file mode 120000 index 0000000..c7660df --- /dev/null +++ b/.#Makefile @@ -0,0 +1 @@ +old@Georgia-MacBook.local.16131 \ No newline at end of file diff --git a/.gitignore b/.gitignore~ similarity index 83% rename from .gitignore rename to .gitignore~ index 67aa07d..368d9bd 100644 --- a/.gitignore +++ b/.gitignore~ @@ -1,4 +1,3 @@ futuregrid.virtual.cluster.egg-info dist -*~ -*.pyc \ No newline at end of file +*~ \ No newline at end of file diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ + diff --git a/LICENCE.txt b/LICENCE.txt deleted file mode 100644 index 12bc4b0..0000000 --- a/LICENCE.txt +++ /dev/null @@ -1,14 +0,0 @@ -Code is distributed under Apache License - -Please do not remove this file - -Institution: -Indiana University - -Authors: -Gregor von Laszewski -Xiuwen Yang - -e-mail: -laszewski@gmail.com -xiuwyang@umail.iu.edu diff --git a/README.md b/README.md deleted file mode 100644 index da0fe87..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -PLEASE SEE: - -* https://github.com/futuregrid/virtual-cluster/blob/master/doc/source/intro.rst \ No newline at end of file diff --git a/doc/source/download.rst b/_sources/download.txt similarity index 100% rename from doc/source/download.rst rename to _sources/download.txt diff --git a/doc/source/index.rst b/_sources/index.txt similarity index 100% rename from doc/source/index.rst rename to _sources/index.txt diff --git a/doc/source/intro.rst b/_sources/intro.txt similarity index 100% rename from doc/source/intro.rst rename to _sources/intro.txt diff --git a/doc/source/modules.rst b/_sources/modules.txt similarity index 100% rename from doc/source/modules.rst rename to _sources/modules.txt diff --git a/doc/source/support.rst b/_sources/support.txt similarity index 100% rename from doc/source/support.rst rename to _sources/support.txt diff --git a/_static/ajax-loader.gif b/_static/ajax-loader.gif new file mode 100644 index 0000000..61faf8c Binary files /dev/null and b/_static/ajax-loader.gif differ diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..43e8baf --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,540 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/doc/source/_themes/bootstrap/static/bootstrap-responsive.css b/_static/bootstrap-responsive.css similarity index 100% rename from doc/source/_themes/bootstrap/static/bootstrap-responsive.css rename to _static/bootstrap-responsive.css diff --git a/doc/source/_themes/bootstrap/static/bootstrap-sphinx.css_t b/_static/bootstrap-sphinx.css similarity index 61% rename from doc/source/_themes/bootstrap/static/bootstrap-sphinx.css_t rename to _static/bootstrap-sphinx.css index e6f6ad3..ad7747d 100644 --- a/doc/source/_themes/bootstrap/static/bootstrap-sphinx.css_t +++ b/_static/bootstrap-sphinx.css @@ -128,84 +128,6 @@ pre { word-wrap: normal; white-space: pre; } - -{%- if theme_page_toc_position == 'subnav' %} -.subnav { - margin-top: 18px; -} -.subnav .nav-pills { - overflow: hidden; - white-space: nowrap; -} -.subnav .nav-pills > li { - display: table-cell; - float: none; -} -.subnav .navbar-inner { - padding: 0; -} -.subnav .navbar-inner ul.nav { - white-space: nowrap; -} -.subnav .navbar-inner ul.nav li { - display: inline-block; - float: none; -} -.subnav .dropdown-toggle { - display: none; - margin-left: 8px; - line-height: 40px; -} -.subnav .hoverscroll { - margin: 0 auto; -} -.subnav-fixed .nav { - margin: 0; -} -@media (min-width: 980px) { - .subnav-fixed { - margin-top: 0; - position: fixed; - top: 40px; - left: 0; - right: 0; - z-index: 1020; /* 10 less than .navbar-fixed to prevent any overlap */ - border-color: #d5d5d5; - border-width: 0 0 1px; /* drop the border on the fixed edges */ - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - -webkit-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1); - -moz-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1); - box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */ - } - .subnav-fixed .nav { - width: 938px; - margin: 0 auto; - padding: 0 1px; - } - .subnav .nav > li:first-child > a, - .subnav .nav > li:first-child > a:hover { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - } -} -@media (max-width: 768px) { - .subnav { - display: none; - } -} -@media (min-width: 1210px) { - .subnav-fixed .nav { - width: 1168px; - } - -} -{%- endif %} - -{%- if theme_page_toc_position.startswith('sidebar-') %} .page-sidebar { max-width: 300px; } @@ -233,13 +155,7 @@ pre { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); background-color: #0088cc; } -{%- endif %} - -{%- block sidebarlogo %} - {%- if logo %} .navbar h3 a, .navbar .brand { - background: transparent url("{{ logo }}") no-repeat 22px 3px; + background: transparent url("fg-logo-white-24x36.png") no-repeat 22px 3px; padding-left: 62px; -} - {%- endif %} -{%- endblock %} +} \ No newline at end of file diff --git a/doc/source/_themes/bootstrap/static/bootstrap-sphinx.js_t b/_static/bootstrap-sphinx.js similarity index 55% rename from doc/source/_themes/bootstrap/static/bootstrap-sphinx.js_t rename to _static/bootstrap-sphinx.js index 6abca9c..cd31723 100644 --- a/doc/source/_themes/bootstrap/static/bootstrap-sphinx.js_t +++ b/_static/bootstrap-sphinx.js @@ -15,79 +15,6 @@ sphinx['init'] = function($win, $nav, $subnav){ } return {left: 0, top: 0}; }; - - {%- if theme_page_toc_position == 'subnav' %} - /** - * Initialize the subnav functionality, handling fixing it to the top when - * scrolling, dealing with overflow, etc. - */ - sphinx['initSubNav'] = function(){ - if (!$subnav.length){ - return; - } - - // disable until it can be fixed to work w/ hoverscroll - /*$subnav.find('.dropdown-toggle').show().click(function(e){ - e.stopPropagation(); - $(this).closest('li').toggleClass('open'); - });*/ - - function afix(){ - $subnav.addClass('subnav-fixed'); - if ($subnav.css('position') == 'fixed'){ - if (!$subnav.siblings('.subnav-placeholder').length){ - var $placeholder = $('
'); - $placeholder.height($subnav.outerHeight()); - $subnav.before($placeholder); - } - } - } - - function unafix(){ - $subnav.siblings('.subnav-placeholder').remove(); - $subnav.removeClass('subnav-fixed'); - } - - function hoverscroll(){ - var $subnav_inner = $subnav.find('.navbar-inner'); - var $list = $subnav_inner.find('ul').first(); - var width = ($subnav.css('position') == 'fixed') ? - $('.body').width() : $subnav_inner.width(); - $list.hoverscroll({ - arrowOpacity: 0.3, - width: width, - height: $subnav_inner.height() - }); - } - - var navTop = 0; - $win.on('load resize', function(e){ - var offset = sphinx.absolutePosition($('.subnav-placeholder').get(0) || $subnav.get(0)); - navTop = offset.top - $nav.outerHeight(); - $win.scroll(); - if (e.type == 'load'){ - $win.hashchange(); - } - }); - $win.on('hoverscroll scroll', function(){ - var scrollTop = $win.scrollTop(); - if (scrollTop > navTop) { - if (!$subnav.hasClass('subnav-fixed')){ - // account for the page not being long enough to make the subnav fixed - // without it immediately scrolling back and forcing back to static - // positioning. - var height = $(document).height() - navTop - $subnav.outerHeight(true); - if (height > $win.height()){ - afix(); - } - } - } else if ($subnav.hasClass('subnav-fixed')){ - unafix(); - } - hoverscroll(); - }).scroll(); - }; - {%- elif theme_page_toc_position.startswith('sidebar-') %} /** * Initialize the sidebar to support fixed positioning. */ @@ -99,7 +26,6 @@ sphinx['init'] = function($win, $nav, $subnav){ }).resize(); $sidenav.affix(); }; - {%- endif %} /** * Initialize top nav flyout menus to position the submenus more @@ -169,19 +95,6 @@ sphinx['init'] = function($win, $nav, $subnav){ navOffset += $nav.outerHeight(); } - {%- if theme_page_toc_position == 'subnav' %} - var $placeholder = $subnav.siblings('.subnav-placeholder'); - if ($placeholder.length){ - navOffset += $placeholder.outerHeight(); - }else{ - $subnav.addClass('subnav-fixed'); - if ($subnav.css('position') == 'fixed'){ - navOffset += $subnav.outerHeight(); - } - $subnav.removeClass('subnav-fixed'); - } - {%- endif %} - $win.scrollTop(sphinx.absolutePosition($target.get(0)).top - navOffset); var $animate = $target; if ($animate.is('.section')){ @@ -192,11 +105,6 @@ sphinx['init'] = function($win, $nav, $subnav){ $animate.animate({opacity: 0.4}, 1000, function(){ $animate.animate({opacity: 1}, 1000); }); - {%- if theme_page_toc_position == 'subnav' %} - if ($subnav.css('position') == 'fixed'){ - $win.scroll(); - } - {%- endif %} } }).hashchange(); }; @@ -204,9 +112,6 @@ sphinx['init'] = function($win, $nav, $subnav){ sphinx['initScrollspy'] = function(){ var navOffset = 10; navOffset += $nav.outerHeight(); - {%- if theme_page_toc_position == 'subnav' %} - navOffset += $subnav.outerHeight(); - {%- endif %} // wait on all other events setTimeout(function(){ @@ -214,39 +119,12 @@ sphinx['init'] = function($win, $nav, $subnav){ $win.on('load resize', function(){ $('body').scrollspy('refresh'); }); - - {%- if theme_page_toc_position == 'subnav' %} - $('body').bind('activate', function(e){ - var $target = $(e.target); - var $list = $subnav.find('.listcontainer'); - var left = $target.position().left; - var right = left + $target.width(); - var scroll = $list.scrollLeft(); - - if (right > ($list.width() - scroll)){ - $list.scrollLeft(right); - $win.trigger('hoverscroll'); - }else if (left < scroll){ - $list.scrollLeft(left); - $win.trigger('hoverscroll'); - } - }); - $win.on('scroll', function(){ - if($win.scrollTop() == 0){ - $('body').data('scrollspy').activeTarget = null; - $subnav.find('li.active').removeClass('active'); - $subnav.find('.listcontainer').scrollLeft(0); - $win.trigger('hoverscroll'); - } - }); - {%- else %} $win.on('scroll', function(){ if($win.scrollTop() == 0){ $('body').data('scrollspy').activeTarget = null; $('.page-toc').find('li.active').removeClass('active'); } }); - {%- endif %} }, 0); }; @@ -277,11 +155,7 @@ sphinx['init'] = function($win, $nav, $subnav){ }); sphinx.initTopNav(); - {%- if theme_page_toc_position == 'subnav' %} - sphinx.initSubNav(); - {%- elif theme_page_toc_position.startswith('sidebar-') %} sphinx.initSideNav(); - {%- endif %} sphinx.initHashLinks(); sphinx.initScrollspy(); @@ -289,4 +163,4 @@ sphinx['init'] = function($win, $nav, $subnav){ $(document).ready(function() { sphinx.init($(window), $('#navbar'), $('.subnav')); -}); +}); \ No newline at end of file diff --git a/doc/source/_themes/bootstrap/static/bootstrap.css b/_static/bootstrap.css similarity index 100% rename from doc/source/_themes/bootstrap/static/bootstrap.css rename to _static/bootstrap.css diff --git a/doc/source/_themes/bootstrap/static/bootstrap.js b/_static/bootstrap.js similarity index 100% rename from doc/source/_themes/bootstrap/static/bootstrap.js rename to _static/bootstrap.js diff --git a/doc/source/_themes/bootstrap/static/bootstrap.scrollspy.js b/_static/bootstrap.scrollspy.js similarity index 100% rename from doc/source/_themes/bootstrap/static/bootstrap.scrollspy.js rename to _static/bootstrap.scrollspy.js diff --git a/_static/comment-bright.png b/_static/comment-bright.png new file mode 100644 index 0000000..551517b Binary files /dev/null and b/_static/comment-bright.png differ diff --git a/_static/comment-close.png b/_static/comment-close.png new file mode 100644 index 0000000..09b54be Binary files /dev/null and b/_static/comment-close.png differ diff --git a/_static/comment.png b/_static/comment.png new file mode 100644 index 0000000..92feb52 Binary files /dev/null and b/_static/comment.png differ diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..d4619fd --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,247 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +} + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * small function to check if an array contains + * a given item. + */ +jQuery.contains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == item) + return true; + } + return false; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('' + _('Hide Search Matches') + '
') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/_static/down-pressed.png b/_static/down-pressed.png new file mode 100644 index 0000000..6f7ad78 Binary files /dev/null and b/_static/down-pressed.png differ diff --git a/_static/down.png b/_static/down.png new file mode 100644 index 0000000..3003a88 Binary files /dev/null and b/_static/down.png differ diff --git a/doc/source/_static/fg-logo-grid-glow.png b/_static/fg-logo-grid-glow.png similarity index 100% rename from doc/source/_static/fg-logo-grid-glow.png rename to _static/fg-logo-grid-glow.png diff --git a/doc/source/_static/fg-logo-white-24x36.png b/_static/fg-logo-white-24x36.png similarity index 100% rename from doc/source/_static/fg-logo-white-24x36.png rename to _static/fg-logo-white-24x36.png diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..d18082e Binary files /dev/null and b/_static/file.png differ diff --git a/doc/source/_static/header_bg.png b/_static/header_bg.png similarity index 100% rename from doc/source/_static/header_bg.png rename to _static/header_bg.png diff --git a/doc/source/_themes/bootstrap/static/img/arrow-left.png b/_static/img/arrow-left.png similarity index 100% rename from doc/source/_themes/bootstrap/static/img/arrow-left.png rename to _static/img/arrow-left.png diff --git a/doc/source/_themes/bootstrap/static/img/arrow-right.png b/_static/img/arrow-right.png similarity index 100% rename from doc/source/_themes/bootstrap/static/img/arrow-right.png rename to _static/img/arrow-right.png diff --git a/doc/source/_themes/bootstrap/static/img/glyphicons-halflings-white.png b/_static/img/glyphicons-halflings-white.png similarity index 100% rename from doc/source/_themes/bootstrap/static/img/glyphicons-halflings-white.png rename to _static/img/glyphicons-halflings-white.png diff --git a/doc/source/_themes/bootstrap/static/img/glyphicons-halflings.png b/_static/img/glyphicons-halflings.png similarity index 100% rename from doc/source/_themes/bootstrap/static/img/glyphicons-halflings.png rename to _static/img/glyphicons-halflings.png diff --git a/doc/source/_themes/bootstrap/static/jquery.ba-hashchange.js b/_static/jquery.ba-hashchange.js similarity index 100% rename from doc/source/_themes/bootstrap/static/jquery.ba-hashchange.js rename to _static/jquery.ba-hashchange.js diff --git a/doc/source/_themes/bootstrap/static/jquery.hoverscroll.css b/_static/jquery.hoverscroll.css similarity index 100% rename from doc/source/_themes/bootstrap/static/jquery.hoverscroll.css rename to _static/jquery.hoverscroll.css diff --git a/doc/source/_themes/bootstrap/static/jquery.hoverscroll.js b/_static/jquery.hoverscroll.js similarity index 100% rename from doc/source/_themes/bootstrap/static/jquery.hoverscroll.js rename to _static/jquery.hoverscroll.js diff --git a/doc/source/_themes/bootstrap/static/jquery.js b/_static/jquery.js similarity index 100% rename from doc/source/_themes/bootstrap/static/jquery.js rename to _static/jquery.js diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000..da1c562 Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000..b3cb374 Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000..d79caa1 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000..663be4c --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,560 @@ +/* + * searchtools.js_t + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words, hlwords is the list of normal, unstemmed + * words. the first one is used to find the occurance, the + * latter for highlighting it. + */ + +jQuery.makeSearchSummary = function(text, keywords, hlwords) { + var textLower = text.toLowerCase(); + var start = 0; + $.each(keywords, function() { + var i = textLower.indexOf(this.toLowerCase()); + if (i > -1) + start = i; + }); + start = Math.max(start - 120, 0); + var excerpt = ((start > 0) ? '...' : '') + + $.trim(text.substr(start, 240)) + + ((start + 240 - text.length) ? '...' : ''); + var rv = $('').text(excerpt); + $.each(hlwords, function() { + rv = rv.highlightText(this, 'highlighted'); + }); + return rv; +} + + +/** + * Porter Stemmer + */ +var Stemmer = function() { + + var step2list = { + ational: 'ate', + tional: 'tion', + enci: 'ence', + anci: 'ance', + izer: 'ize', + bli: 'ble', + alli: 'al', + entli: 'ent', + eli: 'e', + ousli: 'ous', + ization: 'ize', + ation: 'ate', + ator: 'ate', + alism: 'al', + iveness: 'ive', + fulness: 'ful', + ousness: 'ous', + aliti: 'al', + iviti: 'ive', + biliti: 'ble', + logi: 'log' + }; + + var step3list = { + icate: 'ic', + ative: '', + alize: 'al', + iciti: 'ic', + ical: 'ic', + ful: '', + ness: '' + }; + + var c = "[^aeiou]"; // consonant + var v = "[aeiouy]"; // vowel + var C = c + "[^aeiouy]*"; // consonant sequence + var V = v + "[aeiou]*"; // vowel sequence + + var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, success: null, + dataType: "script", cache: true}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (var i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something + */ + performSearch : function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('\ + <%username%>\ + \ + <%time.delta%>\ +
\ +\ + \ + reply ▿\ + proposal ▹\ + \ + \ + \ +
\ +\ +<#proposal_diff#>\ +\ +
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+'''
+CloudInstances.py (python)
+'''
+
+'''
+More ....
+-------------------------
+
+Operations on clusters based on backup file
+
+Backup file is the file which stores information of
+a list of virtual clusters
+
+Backup file format
+-------------------------
+[{cluster1}, {cluster2}, {cluster3}, {}]
+
+Each cluster is a dictionary, it has the following format:
+{'name':'cluster1', 'status':'run', '0':'{}', '1':'{}', ...}
+The first two keys are name, and status, the rest are instances.
+Name represents the name of virtual cluster and status represents
+the status of cluster. Key '0', '1', ... represent instances, each
+instance is associated with a number which can be used to refer to.
+Instance key starts at 0.
+
+Each instance is a dictionary, it has the following format:
+{'id':'', 'image':'', 'type':'', 'ip':''}
+'''
+
+import pickle
+import os
+
+
+[docs]class CloudInstances:
+ '''
+ Class CloudInstances
+ '''
+
+ cloud_instances = {}
+ backup_file = None
+ RUN = 'run'
+ SAVED = 'saved'
+ DOWN = 'terminated'
+
+ def __init__(self):
+ self.clear()
+ return
+
+[docs] def set_backup_file(self, filename):
+ '''
+ Sets backup file for reading and writing
+
+ Parameters:
+ filename -- backup file name
+
+ Logic:
+ Checks if the backup file has the correct format by
+ checking if all keys are present in each dictionary.
+
+ Return:
+ true -- If can not find backup file, so the file has not
+ been created.
+ false -- If file exists, but can not find all the keys
+ which are needed.
+ '''
+
+ self.backup_file = filename
+
+ # check if file is old version or corrupted
+ try:
+ src_file = open(os.path.expanduser(self.backup_file), "r")
+ cloud_list = pickle.load(src_file)
+ for cloud in cloud_list:
+ # check basic info
+ if not 'name' in cloud or \
+ not 'status' in cloud:
+ return False
+ # if is an instance, check all the keys
+ for element in cloud.values():
+ if type(element) is dict:
+ if not 'id' in element or \
+ not 'image' in element or \
+ not 'type' in element or \
+ not 'ip' in element:
+ return False
+ except (KeyError, TypeError):
+ return False
+ except IOError:
+ return True
+ return True
+
+[docs] def set_cloud_instances_by_name(self, name):
+ '''
+ Sets a new cloud instance
+
+ Parameters:
+ name -- virtual cluster name
+
+ Logic:
+ Granted this cluster is new and about to be created,
+ sets cluster name to name, and sets status to run.
+
+ No returns
+ '''
+
+ self.cloud_instances['name'] = name
+ # if create cluster, set status to RUN
+ self.cloud_instances['status'] = self.RUN
+
+[docs] def get_cloud_instances_by_name(self, name):
+ '''
+ Gets cloud instance list by cluster name
+
+ Parameters:
+ name -- virtual cluster name
+
+ Logic:
+ Granted we check cluster existence every time before
+ each operation (create, save, restore, terminate, ...)
+ so here no need to check if backup file is existed
+
+ Opens backup file for reading, loads cluster lists,
+ find cluster instances list given cluster name, then
+ sets cloud_instances to the cluster loaded
+
+ No returns
+ '''
+
+ # return cluster by given name
+ # using key 'name'
+ src_file = open(os.path.expanduser(self.backup_file), "r")
+ cloud_list = pickle.load(src_file)
+ for cloud in cloud_list:
+ if cloud['name'] == name:
+ self.cloud_instances = cloud
+
+[docs] def checkpoint_cloud_instances(self,
+ cluster_name,
+ control_node_id,
+ compute_node_id,
+ instance_type,
+ cluster_size):
+ '''
+ Sets cluster saving information after actually saves it
+
+ Parameters:
+ cluster_name -- virtual cluster name
+ control_node_id -- control node image id
+ compute_node_id -- compute node image id
+ instance_type -- instance type
+ cluster_size -- size of cluster (control node not included)
+
+ Logic:
+ Sets all possible values that could be used to restore cluster
+ in the future before saves it into backup file
+
+ Sets cluster status to SAVED
+
+ No returns
+ '''
+
+ # if save cluster, set basic info in backupfile
+ # resotre this cluster based on this info
+ self.clear()
+ self.cloud_instances['name'] = cluster_name
+ self.cloud_instances['control'] = control_node_id
+ self.cloud_instances['compute'] = compute_node_id
+ self.cloud_instances['type'] = instance_type
+ self.cloud_instances['size'] = cluster_size
+ self.cloud_instances['status'] = self.SAVED
+
+[docs] def get_all_cloud_instances(self):
+ '''
+ Gets all cloud instances lists
+
+ No parameters
+
+ Logic:
+ Loads all cloud instances from backup file.
+
+ Return:
+ cloud_list -- if file exists, return all the cloud lists
+ saved
+ [] -- if file does not exist
+ '''
+
+ try:
+ src_file = open(os.path.expanduser(self.backup_file), "r")
+ cloud_list = pickle.load(src_file)
+ return cloud_list
+ except IOError:
+ return []
+
+[docs] def get_cluster_size(self, cluster=None):
+ '''
+ Gets virtual cluster size
+
+ Parameters:
+ cluster -- virtual cluster name
+ default: None
+
+ Logic:
+ Default cloud instance is the dictionary self.cloud_instances,
+ if cluster is specified, then returns the size of cluster given
+ name.
+
+ Given cluster_instance has the format of:
+ {'name':'', 'status':'', '0':'', '1':'',...}
+ The size of cluster is the length of this dictionary subtracts
+ length of name and status which is 2
+
+ Return:
+ length of self.cloud_instances -- if cluster is set to default
+ length of cluster -- if cluster is set to any other clusters
+ '''
+
+ # get cluster size
+ # not include name and status fileds
+ if not cluster:
+ return len(self.cloud_instances) - 2
+ else:
+ return len(cluster) - 2
+
+[docs] def get_list(self):
+ '''
+ Gets cloud instances list
+
+ No parameters
+
+ Return:
+ cloud instances list -- self.cloud_instances
+ '''
+
+ return self.cloud_instances
+
+[docs] def get_index(self, instance):
+ '''
+ Gets instance key from cloud instance list
+
+ Parameters:
+ instance -- an instance dictionary
+
+ Logic:
+ Finds the instance matches the id
+ then return the key
+
+ Return:
+ key -- instance index
+ '''
+ for key, element in self.cloud_instances.items():
+ # if element is an instance
+ if type(element) is dict:
+ if element['id'] == instance['id']:
+ return key
+
+[docs] def set_instance(self,
+ instance_id,
+ image_id,
+ instance_type,
+ instance_ip='',
+ index=None):
+ '''
+ Sets attributes of a given instance
+
+ Parameters:
+ instance_id -- instance id
+ image_id -- image id
+ instance_type -- instance type
+ instance_ip -- public IP associated with instance
+ default: ''
+
+ Logic:
+ Set id, image, type, IP for one instance
+ Using current cluster size as key
+ So first instance has key 0, second has key 1 ...
+
+ No returns
+ '''
+
+ instance = {}
+ instance['id'] = instance_id
+ instance['image'] = image_id
+ instance['type'] = instance_type
+ instance['ip'] = instance_ip
+ # instance key '0, 1, 2...'
+ # can use index to get instance
+ if index == None:
+ self.cloud_instances[self.get_cluster_size()] = instance
+ else:
+ self.cloud_instances[index] = instance
+
+[docs] def del_instance(self, instance):
+ '''
+ Deletes an instance from cloud instance list
+
+ Parameters:
+ instance -- an instance dictionary
+
+ Logic:
+ Find the instance matches id then delete
+ the instance using key
+
+ No returns
+ '''
+
+ for key, element in self.cloud_instances.items():
+ # if element is an instance
+ if type(element) is dict:
+ if element['id'] == instance['id']:
+ del self.cloud_instances[key]
+
+[docs] def set_ip_by_id(self, instance_id, instance_ip):
+ '''
+ Sets IP by given instance id
+
+ Parameters:
+ instance_id -- instance id
+ instance_ip -- public IP associated with instance
+
+ Logic:
+ Loop all values in cloud_instances, given an instance
+ is a dictionary, if current element is a dictionary,
+ check key 'id', and then set 'ip' if key 'id' matches
+
+ No returns
+ '''
+
+ for element in self.cloud_instances.values():
+ # if element is an instance
+ if type(element) is dict:
+ if element['id'] == instance_id:
+ element['ip'] = instance_ip
+
+[docs] def clear(self):
+ '''
+ Clears cloud instances list
+
+ No parameters
+ No returns
+ '''
+
+ self.cloud_instances = {}
+
+[docs] def get_by_id(self, cloud_id):
+ '''
+ Gets instance by index
+
+ Parameters:
+ cloud_id -- instance index, which is also the key for instance
+
+ Logic:
+ Given that we use current size of cluster as key, so we can use
+ this index to get one specific instance
+
+ Return:
+ Instance dictionary -- an instance which has the format of
+ {'id':'', 'image':'', 'type':'', 'ip':''}
+ '''
+
+ return self.cloud_instances[cloud_id]
+
+[docs] def save_instances(self):
+ '''
+ Writes current cloud instances list into backup file
+
+ No parameters
+
+ Logic:
+ Opens backup file to save cloud_instance
+ If file does not exist, then creates backup file, dumps
+ cloud_instances into the file
+ If file exists, then loads the content from the file,
+ combine all cloud instance into list, dump this list into
+ file. So the list has the format of [{cluster1},{cluster2},{}]
+ (may need to change it to dictionary)
+
+ No returns
+ '''
+
+ try:
+ # open file for reading
+ src_file = open(os.path.expanduser(self.backup_file), "r")
+ instance_list = pickle.load(src_file)
+ # combine existing clusters and current cluster into list
+ instance_list.insert(0, self.cloud_instances)
+ src_file = open(os.path.expanduser(self.backup_file), "w")
+ pickle.dump(instance_list, src_file)
+ src_file.close()
+
+ except IOError:
+ # if cannot find this file, then create the file
+ if not os.path.exists(os.path.expanduser
+ (os.path.split(self.backup_file)[0])):
+ os.makedirs(os.path.expanduser
+ (os.path.split(self.backup_file)[0]))
+ src_file = open(os.path.expanduser(self.backup_file), "w")
+ # directly save cluster into backup file
+ pickle.dump([self.cloud_instances], src_file)
+ src_file.close()
+
+[docs] def if_exist(self, name):
+ '''
+ Checks if a given cluster name exists
+
+ Parameters:
+ name -- cluster name
+
+ Logic:
+ Given all clusters stored in backup file is a list, so loop
+ this list, and check key 'name' in each cluster dictionary
+
+ Return:
+ true -- if find a cluster matches the cluster name given as
+ parameter
+ false -- if 1) file does not exist
+ 2) Cannot find a cluster matches the cluster
+ name given as parameter after checking
+ all clusters saved in the backup file
+ '''
+
+ try:
+ src_file = open(os.path.expanduser(self.backup_file), "r")
+ cloud_list = pickle.load(src_file)
+ for cloud in cloud_list:
+ if cloud['name'] == name:
+ return True
+ return False
+
+ except IOError:
+ return False
+
+[docs] def del_by_name(self, name):
+ '''
+ Deletes cloud instances list from backup file given name
+
+ Parameter:
+ name -- cluster name
+
+ Logic:
+ Loads content from backup file, given content is list, then
+ delete the cluster which matches name using remove, then
+ dump the new content into backup file
+
+ No returns
+ '''
+
+ src_file = open(os.path.expanduser(self.backup_file), "r")
+ cloud_list = pickle.load(src_file)
+ for cloud in cloud_list:
+ if cloud['name'] == name:
+ # remove cluster if game matches
+ cloud_list.remove(cloud)
+ src_file = open(os.path.expanduser(self.backup_file), "w")
+ pickle.dump(cloud_list, src_file)
+ src_file.close()
+ return
+
+[docs] def set_status(self, status):
+ '''
+ Gets status of virtual cluster
+
+ Parameters:
+ status -- cluster status (run, saved, terminated)
+
+ Logic:
+ Sets the status in cloud instance list
+
+ No returns
+ '''
+
+ self.cloud_instances['status'] = status
+
+[docs] def if_status(self, status):
+ '''
+ Checks status of virtual cluster
+
+ Parameters:
+ status -- cluster status (run, saved, terminated)
+
+ Logic:
+ checks if status in cluster instance list matches
+ status given as parameter
+
+ Return:
+ true -- if status of cluster instance list matches
+ the status given as parameter
+ false -- if status of cluster instance list does
+ not match the status given as parameter
+ '''
+
+ return self.cloud_instances['status'] == status
+
+[docs] def get_status(self):
+ '''
+ Gets cluster status
+
+ Parameters:
+ status -- cluster status (run, saved, terminated)
+
+ Logic:
+ Get the value of key 'status'
+
+ Return:
+ Returns the value of status
+ '''
+
+ return self.cloud_instances['status']
+
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+'''
+FGCluster command
+'''
+
+"""
+FGCluster.py (python)
+-------------------------
+
+Operations for managing virtual clusters
+
+Name fg-cluster
+
+Description
+
+usage:
+
+ fg-cluster <parameters>
+
+ -f -- futuregrid configuration file
+ which specifies the location of
+ necessary files to run program
+
+ --debug -- show debug message
+
+ --default-repository -- set if to use IU ubuntu repo
+ if specifyed, then not use IU
+ ubuntu repo
+
+ --create-key -- create euca key file
+
+ subcommands:
+
+ run: run a virtual cluster of given parameters
+
+ -a -- virtual cluster name
+ -n -- compute node number
+ -i -- image type
+ -t -- instance type
+
+ checkpoint: save the state of currently running cluster
+
+ -a -- virtual cluster name
+ -c -- control node bucket name
+ -t -- control node image name
+ -m -- compute node bucket name
+ -e -- compute node image name
+
+ restore: restore state of saved virtual cluster
+
+ -a -- virtual cluster name
+
+ terminate: terminate a virtual cluster of given name
+
+ -a -- virtual cluster name
+
+ status: show status of virtual cluster(s)
+
+ -a -- virtual cluster name
+
+ If no virtual cluster name is specified, the command will return
+ status of all virtual clusters recorded
+
+ list: list virtual clusters
+"""
+
+import argparse
+import sys
+import os
+import time
+import threading
+import ConfigParser
+import random
+import Queue
+import re
+import boto.ec2
+
+from boto.ec2.connection import EC2Connection
+from futuregrid.virtual.cluster.CloudInstances import CloudInstances
+#from CloudInstances import CloudInstances
+from subprocess import Popen, PIPE
+from ConfigParser import NoOptionError
+from ConfigParser import MissingSectionHeaderError
+from ConfigParser import NoSectionError
+
+
+[docs]class Cluster(object):
+ """
+ Class Cluster
+ -------------
+ Methods to
+ run virtual cluster
+ checkpoint virtual cluster
+ restore virtual cluster
+ terminate virtual cluster
+ show status of virtual cluster(s)
+ list virtual clusters
+ """
+
+ userkey = None
+ cloud_instances = None
+ backup_file = None
+ user = None
+ enrc = None
+ slurm = None
+ mutex = None
+ interface = None
+ cloud = None
+ ec2_conn = None
+ user_login = None
+
+ # debug switch
+ if_debug = False
+ # if false, using IU ubuntu repository
+ if_default = False
+ # if true, create userkey key
+ if_create_key = False
+
+ # repo file name
+ sources_list = 'sources.list'
+
+ def __init__(self):
+ '''
+ init method
+ '''
+ super(Cluster, self).__init__()
+ self.cloud_instances = CloudInstances()
+ self.mutex = threading.Lock()
+
+# ---------------------------------------------------------------------
+# METHODS TO PRINT HELP MESSAGES
+# ---------------------------------------------------------------------
+
+ @classmethod
+[docs] def msg(cls, message):
+ '''
+ Method for printing help messages
+
+ Parameters:
+ message -- message to print
+
+ Logic:
+ use print to print message
+
+ No returns
+ '''
+
+ print message
+
+[docs] def print_section(self, message):
+ '''
+ Prints section header
+ '''
+
+ self.msg('\n=========================')
+ self.msg(message)
+ self.msg('=========================')
+
+[docs] def debug(self, message):
+ '''
+ Method for printing debug message
+
+ Parameter:
+ message -- message to print
+
+ Logic:
+ Checks debug flag, if debug flag is set to
+ true, then prints debug message, if is set
+ to false, then does not print debug message
+
+ No returns
+ '''
+
+ if self.if_debug:
+ print message
+
+# ---------------------------------------------------------------------
+# METHODS TO SET PROGRAM FLAGS
+# ---------------------------------------------------------------------
+[docs] def set_interface(self, interface):
+ '''
+ Set interface the program to use
+
+ Parameter:
+ interface -- interface (boto or euca2ools)
+
+ command line argument --interface will
+ overwrite the configure in configuration file
+
+ No returns
+ '''
+
+ if interface:
+ self.interface = interface
+ if self.interface not in ('euca2ools', 'boto'):
+ self.msg('Interface should be boto or euca2ools')
+ sys.exit()
+ if self.interface == 'boto':
+ self.ec2_connect(self.cloud)
+
+[docs] def set_cloud(self, cloud):
+ '''
+ Set cloud
+
+ Parameter:
+ cloud -- cloud (nova or eucalyptus)
+
+ command line argument --cloud will
+ overwrite the configure in configuration file
+
+ No returns
+ '''
+
+ if cloud:
+ self.cloud = cloud
+ if self.cloud not in ('nova', 'eucalyptus'):
+ self.msg('Cloud should be boto or euca2ools')
+ sys.exit()
+ if self.cloud == 'nova':
+ self.user_login = 'ubuntu'
+ elif self.cloud == 'eucalyptus':
+ self.user_login = 'root'
+
+[docs] def set_flag(self, args):
+ '''
+ Sets control flags
+
+ Parameter:
+ args -- this method deals args.if_debug, if_default
+ if_create_key.
+
+ if_debug: default is false
+ true to print debug message
+ false not to print
+
+ if_default: default is false
+ true to use default ubuntu repository
+ false to use IU ubuntu repository
+
+ if_create_key: default is false
+ true to create key for users
+ false not to create
+ if_boto: default is false
+ true to use boto interface
+ false to use euca2ools
+
+ Logic:
+ Sets control flags
+
+ No returns
+ '''
+
+ self.if_debug = args.debug
+ self.if_default = args.default_repository
+ self.if_create_key = args.create_key
+
+# ---------------------------------------------------------------------
+# METHODS ABOUT KEYPAIRS
+# ---------------------------------------------------------------------
+[docs] def if_keypair_exits(self, name):
+ '''
+ Checks if key is already created
+
+ Parameters:
+ name -- key name
+
+ Logic:
+ Uses euca-describe-keypairs to check the keys created,
+ finds name in the command result
+
+ Return:
+ true -- if finds the key given name as parameter
+ false -- if does not fine the key given name as parameter
+ '''
+
+ command = 'euca-describe-keypairs'
+ for element in self.get_command_result(command).split('\t'):
+ if element.find(name) >= 0:
+ return True
+ return False
+
+ @classmethod
+[docs] def add_keypair(cls, name, key):
+ '''
+ Adds keypair
+
+ Parameter:
+ name -- userkey name
+ key -- location of userkey
+
+ Logic:
+ Uses euca-add-keypair to create key for user, key will
+ be created under the location referred by key
+
+ No returns
+ '''
+
+ os.system('euca-add-keypair %s > %s' % (name, key))
+ os.chmod(os.path.expanduser(key), 0600)
+
+# ---------------------------------------------------------------------
+# METHOD TO PARSE CONFIGURATION FILE
+# ---------------------------------------------------------------------
+
+[docs] def parse_conf(self, file_name='unspecified'):
+ """
+ Parse configuration file
+
+ Parameters:
+ file_name -- configuration file name
+ default: unspecified
+
+ Logic:
+ Parse configuration file if given, default location
+ '~/.futuregrid/futuregrid.cfg'. If no file is given,
+ finds configuration file by following order:
+ 1) finds current directory 'futuregrid.cfg'
+ 2) finds in default location
+
+ configuration file format:
+ [virtual-cluster]
+ # Backup file for saving and loading virtual cluster(s)
+ backup = ~/.futuregrid/virtual-cluster
+ # Slurm configuration input file
+ slurm = ~/.futuregrid/slurm.conf.in
+ # userkey pem file
+ userkey = ~/PUT-YOUR-USER-NAME.pem
+ # environment file
+ enrc = ~/novarc
+
+ Checks if all files specified in configuration
+ file are present.If create-key is set to true,
+ first check if key exists, if key does not exist,
+ then creates key under the location where is specified
+ by userkey in the configuration file
+
+ Due to different version of this tool, back-end
+ structure of backup file may change, so checks
+ if backup file has the correct format before the start
+
+ Checks all possible errors about configuration file
+
+ No returns
+ """
+
+ config = ConfigParser.ConfigParser()
+ try:
+ # default location ~/.futuregrid/futuregrid.cfg
+ # read[], reads begin from last to first
+ # so, first reads file_name, then current directory, then default
+ config.read([os.path.expanduser('~/.futuregrid/futuregrid.cfg'),
+ 'futuregrid.cfg',
+ os.path.expanduser(file_name)])
+
+ self.backup_file = config.get('virtual-cluster', 'backup')
+ self.debug('Backup file %s' % self.backup_file)
+ self.userkey = config.get('virtual-cluster', 'userkey')
+ self.debug('Userkey %s' % self.userkey)
+ self.user = os.path.splitext(self.userkey.split('/')[-1])[0]
+ self.enrc = config.get('virtual-cluster', 'enrc')
+ self.debug('enrc %s' % self.enrc)
+ self.slurm = config.get('virtual-cluster', 'slurm')
+ self.debug('SLURM configuration input file %s' % self.slurm)
+ self.interface = config.get('virtual-cluster', 'interface')
+ self.cloud = config.get('virtual-cluster', 'cloud')
+
+ # checking if all file are present
+ self.debug('Checking if all file are present')
+ if not os.path.exists(os.path.expanduser(self.userkey)):
+ if self.if_create_key:
+ # create key for user
+# self.add_keypair(self.user)
+ if self.if_keypair_exits(self.user):
+ self.msg('\nYou have userkey %s.pem, please correct'
+ ' its location in configuration file'
+ % self.user)
+ sys.exit()
+ else:
+ self.add_keypair(self.user, self.userkey)
+ else:
+ self.msg('ERROR: You must have userkey file')
+ sys.exit(1)
+ if not os.path.exists(os.path.expanduser(self.enrc)):
+ self.msg('ERROR: You must have novarc file')
+ sys.exit(1)
+ else:
+ self.debug('Reading environment')
+ key_dir = os.path.dirname(self.enrc)
+ if key_dir.strip() == "":
+ key_dir = "."
+ if self.cloud == 'nova':
+ os.environ["NOVA_KEY_DIR"] = key_dir
+ elif self.cloud =='eucalyptus':
+ os.environ['EUCA_KEY_DIR'] = key_dir
+ with open(os.path.expanduser(self.enrc)) as enrc_content:
+ for line in enrc_content:
+ if re.search("^export ", line):
+ line = line.split()[1]
+ parts = line.split("=")
+ value = ""
+ for i in range(1, len(parts)):
+ parts[i] = parts[i].strip()
+ tempvar = os.path.expandvars(parts[i])
+ parts[i] = os.path.expanduser(tempvar)
+ value += parts[i] + "="
+ value = value.rstrip("=")
+ value = value.strip('"')
+ value = value.strip("'")
+ if parts[0] == 'EC2_CERT' or \
+ parts[0] == 'EC2_PRIVATE_KEY' or \
+ parts[0] == 'EUCALYPTUS_CERT':
+ if not os.path.exists(value):
+ self.msg('%s does not exist' % value)
+ sys.exit()
+ os.environ[parts[0]] = value
+
+ if not os.path.exists(os.path.expanduser(self.slurm)):
+ self.msg('ERROR: You must have slurm.conf.in file')
+ sys.exit(1)
+
+ self.debug('Checking backup file')
+ self.backup_file += self.cloud
+ if not self.cloud_instances.set_backup_file(self.backup_file):
+ self.msg('\nBackup file is corrupted, or you are using an old'
+ ' version of this tool. Please delete this backup file'
+ ' and try again.')
+ sys.exit(1)
+ self.debug('Checking done')
+
+ except IOError:
+ self.msg('\nError in reading configuration file!'
+ ' configuration file not created?')
+ sys.exit()
+ except (MissingSectionHeaderError, NoSectionError):
+ self.msg('\nError in reading configuratin file!'
+ ' No section header?')
+ sys.exit()
+ except NoOptionError:
+ self.msg('\nError in reading configuration file!'
+ ' Correct configuration format?')
+ sys.exit()
+ except ValueError:
+ self.msg('\nError in reading configuration file!'
+ ' Correct python version?')
+ sys.exit()
+
+# ---------------------------------------------------------------------
+# METHOD TO INSTALL
+# ---------------------------------------------------------------------
+
+[docs] def check_avaliable(self, instance):
+ '''
+ Check if instance is available
+
+ Parameters:
+ instance -- instance dictionary
+
+ Return:
+ True -- if instance is available
+ False -- if instance is unavailable
+ '''
+
+ cmd = "ssh -i %s %s@%s uname" % (self.userkey,
+ self.user_login,
+ instance['ip'])
+
+ check_process = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
+ status = os.waitpid(check_process.pid, 0)[1]
+ if status == 0:
+ return True
+ else:
+ return False
+
+[docs] def euca_start_new_instance(self, instance, instance_index):
+ '''
+ Starts new instance given old instance info using euca2ools
+
+ Parameter:
+ instance -- old instance
+ instance_index -- instance index
+ Return:
+ new instance
+ '''
+ # try to create a new one
+ self.msg('Creating new instance')
+ self.euca_run_instance(self.user, 1, instance['image'],
+ instance['type'], instance_index)
+ self.debug('Getting free public IPs')
+ # get free IP list
+ ip_lists = self.euca_describe_addresses()
+ time.sleep(2)
+ new_instance = self.cloud_instances.get_by_id(instance_index)
+ self.msg('\nAssociating IP with %s' % new_instance['id'])
+ new_ip = ip_lists[random.randint(0, len(ip_lists) - 1)]
+ while not self.euca_associate_address(new_instance, new_ip):
+ self.msg('Error in associating IP %s with instance %s, '
+ 'trying again' % (new_ip, new_instance['id']))
+ return new_instance
+
+[docs] def boto_start_new_instance(self, instance, instance_index):
+ '''
+ Starts new instance given old instance info using euca2ools
+
+ Parameter:
+ instance -- old instance
+ instance_index -- instance index
+ Return:
+ new instance
+ '''
+
+ reservation = self.boto_run_instances(instance['image'],
+ 1,
+ instance['type'])
+ new_instance = reservation.instances[0]
+ arg1 = new_instance.id
+ arg2 = new_instance.image_id
+ arg3 = new_instance.instance_type
+ self.cloud_instances.set_instance(instance_id=arg1,
+ image_id=arg2,
+ instance_type=arg3,
+ index=instance_index)
+ ip_list = self.boto_describe_addresses()
+ free_public_ip = ip_list[random.randint(0, len(ip_list) - 1)]
+ self.boto_associate_address(new_instance.id, free_public_ip)
+ new_instance.update()
+ return self.cloud_instances.get_by_id(instance_index)
+
+[docs] def euca_change_ip(self, instance):
+ '''
+ Changes public IP address given instance using euca2ools
+ No returns
+ '''
+
+ ip_lists = self.euca_describe_addresses()
+ # disassociate current one
+ self.disassociate_address(instance['ip'])
+ # associate a new random free ip
+ self.debug('Associating new IP on %s' % instance['id'])
+ self.euca_associate_address(instance, ip_lists[random.randint(0,
+ len(ip_lists) - 1)])
+
+[docs] def boto_change_ip(self, instance):
+ '''
+ Changes public IP address given instance using boto
+ No returns
+ '''
+ ip_list = self.boto_describe_addresses()
+ free_public_ip = ip_list[random.randint(0, len(ip_list) - 1)]
+ self.ec2_conn.disassociate_address(instance['ip'])
+ self.get_instance_from_reservation(instance['id']).update()
+ self.boto_associate_address(instance['id'], free_public_ip)
+
+[docs] def installation(self, instance, max_retry, install=True):
+ '''
+ Checks if instances are ready to deploy and installs the
+ softwares on the instance which is ready
+
+ Parameters:
+ instance -- instance dictionary
+ max_retry -- max number of try for checking instance
+ install -- indicates if need to the installation
+ default: true
+
+ Logic:
+ ssh reomote host to check if can succeed
+ If install is set to true, installation of
+ SLURM and OpenMPI will start on VM which is ready
+
+ Each instances associates a count of times for trying
+ If exceeds the max try limit, then gets free public IPs,
+ and associates it with a random new one, reset count to 0
+
+ Break after all threads are done
+
+ No returns
+ '''
+
+ wait_count = 0
+
+ ip_change = False
+
+ # check if ssh port of all VMs are alive for listening
+ while True:
+ while self.if_status(instance['id'], 'pending'):
+ self.msg('Instance %s is pending' % instance['id'])
+ time.sleep(5)
+ if self.check_avaliable(instance):
+ self.mutex.acquire()
+ self.del_known_host(instance['ip'])
+ self.mutex.release()
+ if install:
+ self.deploy_services(instance)
+ break
+ elif self.cloud == 'nova':
+ self.debug('ssh in %s is closed' % instance['ip'])
+ self.msg('Checking %s (%s) availability, '
+ 'trying %d (max try %d)'
+ % (instance['id'], instance['ip'],
+ wait_count, max_retry))
+
+ wait_count += 1
+ if wait_count > max_retry:
+ if self.if_status(instance['id'], 'shutdown') or \
+ self.if_status(instance['id'], 'terminate') or \
+ self.if_status(instance['id'], 'terminated') or \
+ ip_change:
+ # get instance index
+ instance_index = \
+ self.cloud_instances.get_index(instance)
+ self.msg('Instance %s creation failed'
+ % instance['id'])
+ # delete this instance from cloud instance list
+ self.cloud_instances.del_instance(instance)
+ self.terminate_instance(instance['id'])
+ self.mutex.acquire()
+ if self.interface == 'euca2ools':
+ instance = \
+ self.euca_start_new_instance(instance,
+ instance_index)
+ elif self.interface == 'boto':
+ instance = \
+ self.boto_start_new_instance(instance,
+ instance_index)
+ self.mutex.release()
+ wait_count = 0
+ ip_change = False
+ elif self.if_status(instance['id'], 'running'):
+ self.msg('\nTrying different IP address on %s'
+ % instance['id'])
+ # get free ip addresses
+ self.mutex.acquire()
+ if self.interface == 'euca2ools':
+ self.euca_change_ip(instance)
+ elif self.interface == 'boto':
+ self.boto_change_ip(instance)
+ self.mutex.release()
+ self.debug('New IP is %s' % instance['ip'])
+ wait_count = 0
+ ip_change = True
+ time.sleep(1)
+
+# ---------------------------------------------------------------------
+# METHODS TO DO RPCs
+# ---------------------------------------------------------------------
+
+[docs] def get_command_result(self, command):
+ '''
+ Gets command output
+
+ Parameters:
+ command -- shell command
+
+ Return:
+ Command output
+ '''
+ self.debug(command)
+ return os.popen(command).read()
+
+[docs] def execute(self, instance, command):
+ '''
+ Executes a command on the instance
+
+ Parameters:
+ instance -- cloud instance
+ command -- shell command
+
+ No returns
+ '''
+
+ self.debug("ssh -i %s ubuntu@%s '%s'" % (self.userkey,
+ instance['ip'], command))
+ os.system("ssh -i %s %s@%s '%s'" % (self.userkey,
+ self.user_login,
+ instance['ip'],
+ command))
+
+[docs] def copyto(self, instance, filename):
+ '''
+ Copies the named file to the instance
+
+ Parameters:
+ instance -- cloud instance
+ filename -- the name of file to copy
+
+ No returns
+ '''
+
+ self.debug('scp -i %s %s ubuntu@%s:~/' % (self.userkey,
+ filename, instance['ip']))
+ os.system('scp -i %s %s %s@%s:~/' % (self.userkey,
+ filename,
+ self.user_login,
+ instance['ip']))
+
+[docs] def update(self, instance):
+ '''
+ Executes a software update on the specified instance
+
+ Parameters:
+ instance -- cloud instance
+
+ No returns
+ '''
+
+ self.execute(instance, 'sudo apt-get update')
+
+[docs] def install(self, instance, packagenames):
+ '''
+ Installs the package names that are specified
+ (blank separated on the given instance)
+
+ Parameter:
+ instance -- cloud instance
+ packagenames -- software names
+
+ No returns
+ '''
+
+ self.execute(instance, 'sudo apt-get install --yes '
+ + packagenames)
+
+# ---------------------------------------------------------------------
+# METHODS TO CREATE A VIRTUAL CLUSTER
+# ---------------------------------------------------------------------
+
+[docs] def ec2_connect(self, cloud):
+ '''
+ create connection
+ '''
+ if cloud.strip() == 'nova':
+ path = "/services/Cloud"
+ elif cloud.strip() == 'eucalyptus':
+ path = "/services/Eucalyptus"
+
+ endpoint = os.getenv('EC2_URL').lstrip("http://").split(":")[0]
+ try:
+ region = boto.ec2.regioninfo.RegionInfo(name=cloud,
+ endpoint=endpoint)
+ except:
+ self.msg('ERROR: error in getting region information')
+ sys.exit()
+
+ try:
+ self.ec2_conn = EC2Connection(os.getenv("EC2_ACCESS_KEY"),
+ os.getenv("EC2_SECRET_KEY"),
+ is_secure=False,
+ region=region,
+ port=8773, path=path)
+ except:
+ self.msg('ERROR: error in connecting to EC2')
+ sys.exit()
+
+[docs] def boto_describe_addresses(self):
+ '''
+ get all free ip addresses using boto
+ '''
+
+ ip_list = []
+ for address in \
+ self.ec2_conn.get_all_addresses(addresses=None):
+ address_public_ip = address.public_ip
+ address_instance_id = address.instance_id
+ if not address_instance_id:
+ ip_list.append(address_public_ip)
+ return ip_list
+
+[docs] def get_instance_from_reservation(self, instance_id):
+ '''
+ get instance from reservation
+ '''
+
+ r_in = self.ec2_conn.get_all_instances(instance_ids=[instance_id])
+ return r_in[0].instances[0]
+
+[docs] def boto_associate_address(self, instance_id, address_public_ip):
+ '''
+ associate ip address using boto
+ '''
+
+ while True:
+ try:
+ time.sleep(2)
+ self.ec2_conn.associate_address(instance_id, address_public_ip)
+ break
+ except:
+ self.msg('ERROR: Associating ip %s with instance %s failed, '
+ 'trying again' % (address_public_ip, instance_id))
+ self.msg('ADDRESS: %s %s' % (address_public_ip, instance_id))
+ self.cloud_instances.set_ip_by_id(instance_id, address_public_ip)
+
+[docs] def boto_run_instances(self, image, cluster_size, instance_type):
+ '''
+ run instance usign boto
+ '''
+
+ try:
+ return self.ec2_conn.run_instances(image_id=image,
+ min_count=cluster_size,
+ max_count=cluster_size,
+ key_name=self.user,
+ instance_type=instance_type)
+ except:
+ self.msg('ERROR: Error in lunching instances, please try again')
+ self.ec2_conn.close()
+ sys.exit()
+
+[docs] def euca_run_instance(
+ self,
+ userkey,
+ cluster_size,
+ image,
+ instance_type,
+ index=None
+ ):
+ '''
+ runs instances given parameters
+
+ Parameters:
+ userkey -- userkey name
+ cluster_size -- number of nodes
+ image -- image id
+ instance_type -- instance type
+ index -- instance key as index
+ default: None
+
+ Logic:
+ check if all instances are created
+ correctly. program will exit if
+ not all instances required are
+ created, and terminate those which
+ are running already
+
+ No returns
+ '''
+
+ instance_id_list = []
+
+ # get run instances store int instances list
+ self.debug('euca-run-instances -k %s -n %d -t %s %s'
+ % (userkey,
+ cluster_size,
+ instance_type,
+ image))
+ instances = [x for x in
+ self.get_command_result(
+ 'euca-run-instances -k %s'
+ ' -n %d -t %s %s'
+ % (userkey, cluster_size, instance_type,
+ image)).split()]
+ # find created instance, store into list
+ for instance in instances:
+ if instance.find('i-') == 0:
+ self.debug('%s is created' % instance)
+ instance_id_list.append(instance)
+
+ self.debug('Checking if all instances are created')
+ # check if all instances are created correctly
+ if not len(instance_id_list) == cluster_size:
+ self.msg('\nError in creating cluster, please check your input'
+ ' or instance limit exceeded?')
+ for created_instance_id in instance_id_list:
+ self.terminate_instance(created_instance_id)
+ sys.exit()
+ self.debug('Checking done')
+
+ self.debug('Adding instances into cloud instances list')
+ # add instance to instance list
+ for num in range(cluster_size):
+ try:
+ ip_addr = instance_id_list[num]
+ self.debug('Adding instance %s' % ip_addr)
+ self.cloud_instances.set_instance(instance_id=ip_addr,
+ image_id=image,
+ instance_type=instance_type,
+ index=index)
+ except:
+ self.msg('\nError in creating instances.'
+ ' Program will exit')
+ sys.exit()
+
+[docs] def euca_associate_address(self, instance, free_ip):
+ '''
+ Associates instance with ip
+
+ Parameters:
+ instance -- cloud instance
+ free_ip -- free public IP address
+
+ Logic:
+ Associates IP with a given instance, set IP in cloud_instances
+ delete host information in known_hosts if it has
+
+ Return:
+ false: if association failed
+ trueL: if association succeeded
+ '''
+
+ self.debug('euca-associate-address -i %s %s'
+ % (instance['id'], free_ip))
+ if self.get_command_result('euca-associate-address -i %s %s'
+ % (instance['id'],
+ free_ip)).find('ADDRESS') < 0:
+ return False
+ # set ip using instance id
+ self.msg('ADDRESS %s instance %s' % (free_ip, instance['id']))
+ self.cloud_instances.set_ip_by_id(instance['id'], free_ip)
+ # delete host from known_host file in case man-in-middle-attack
+ self.debug('Deleting %s from known host if it already existed'
+ % free_ip)
+ return True
+
+[docs] def disassociate_address(self, current_ip):
+ '''
+ Disassociates IP address
+
+ Parameters:
+ current_ip -- instance IP which is currently in use
+
+ Logic:
+ Uses euca-disassociates-address to disassociates a
+ given IP address
+
+ No returns
+ '''
+
+ self.msg('Disaasociating %s' % current_ip)
+ self.debug('euca-disassociate-address %s' % current_ip)
+ os.system('euca-disassociate-address %s' % current_ip)
+
+[docs] def euca_describe_addresses(self):
+ '''
+ Return list of free public IP addresses
+
+ No parameters
+
+ Logic:
+ Uses euca-describe-addresses to get all the IP addresses,
+ filter those which are currently in use
+
+ Return:
+ ip_list -- a list of free public IP addresses
+ '''
+
+ ip_list = []
+ ips = [x for x in self.get_command_result('euca-describe-addresses'
+ ).split('\n')]
+
+ # store free ip into ip list for return
+ for free_ip in ips:
+ if free_ip.find('i-') < 0 and len(free_ip) > 0:
+ ip_list.append(free_ip.split('\t')[1])
+ return ip_list
+
+[docs] def if_status(self, instance_id, status):
+ '''
+ Check if instance is running
+ '''
+
+ result = self.get_command_result('euca-describe-instances').split('\n')
+
+ for i in result:
+ if i.find(instance_id) >= 0:
+ if i.find(status) >= 0:
+ return True
+ else:
+ return False
+[docs] def euca_get_ip(self, instance_id):
+ '''
+ Get instance public given instance id
+ '''
+ result = self.get_command_result('euca-describe-instances').split('\n')
+ for i in result:
+ if i.find(instance_id) >= 0 and i.find('pending') >= 0:
+ return i.split('\t')[3]
+
+[docs] def create_cluster(self, args):
+ '''
+ Method for creating cluster
+
+ Parameters:
+ args -- this method deals
+ args.name: cluster name
+ args.number: cluster compute nodes number
+ args.typy: instance type
+ args,image: image id
+
+ Logic:
+ Check existence before creation of cluster
+ If cluster was created before, only start creating
+ if status is DOWN
+
+ Save each instance into cloud instance list
+ Associate IP with each instance
+ Create source list file if using IU ubuntu repository
+ Delete source list file after installtion and configuration
+ of SLURM and OpenMPI
+
+ No returns
+ '''
+
+ self.debug('Checking if %s is existed' % args.name)
+ if self.cloud_instances.if_exist(args.name):
+ # if exists, get cloud info by name
+ self.debug('%s is existed, gettting it from backup file'
+ % args.name)
+ self.cloud_instances.get_cloud_instances_by_name(args.name)
+ # if cluster is terminated, delete old info, start over
+ self.debug('Checking if %s is currently down' % args.name)
+ if self.cloud_instances.if_status(self.cloud_instances.DOWN):
+ self.debug('Deleting old info')
+ self.cloud_instances.del_by_name(args.name)
+ self.cloud_instances.clear()
+ else:
+ self.msg('\nError in creating cluster %s,name is in use'
+ % args.name)
+ sys.exit()
+
+ cluster_size = int(args.number) + 1
+ self.debug('Cluster size is (control node included): %d'
+ % cluster_size)
+ self.print_section('Creating virtual cluster')
+ self.msg('cluster name -- %s' % args.name)
+ self.msg('numbe of nodes -- %s' % cluster_size)
+ self.msg('instance type -- %s' % args.type)
+ self.msg('image id -- %s' % args.image)
+
+ self.debug('Creating new cloud instance %s' % args.name)
+ # set cloud instance list
+ self.cloud_instances.set_cloud_instances_by_name(args.name)
+
+ self.debug('Creating cluster')
+ # run instances given parameters
+ if self.interface == 'euca2ools':
+ self.euca_run_instance(self.user, cluster_size, args.image,
+ args.type)
+ # immediatly associate ip after run instance
+ # may lead to error, use sleep
+ time.sleep(3)
+
+ if self.cloud == 'nova':
+ self.debug('Getting free public IPs')
+ # get free IP list
+ ip_lists = self.euca_describe_addresses()
+
+ self.msg('\nAssociating public IP addresses')
+ for i in range(cluster_size):
+ instance = self.cloud_instances.get_by_id(i)
+ time.sleep(1)
+ while not self.euca_associate_address(instance, ip_lists[i]):
+ self.msg('Error in associating IP %s with instance %s, '
+ 'trying again' % (ip_lists[i], instance['id']))
+ # eucalyptus no need to associate ip
+ if self.cloud == 'eucalyptus':
+ for i in range(cluster_size):
+ instance = self.cloud_instances.get_by_id(i)
+ time.sleep(1)
+ self.cloud_instances.set_ip_by_id(instance['id'],
+ self.euca_get_ip(instance['id']))
+
+ elif self.interface == 'boto':
+ reservation = \
+ self.boto_run_instances(args.image, cluster_size, args.type)
+ self.msg('Associating public IPs')
+ ip_index = 0
+ for instance in reservation.instances:
+ instance.update()
+ self.cloud_instances.set_instance(instance.id,
+ instance.image_id,
+ instance.instance_type)
+ free_public_ip = self.boto_describe_addresses()[ip_index]
+ ip_index += 1
+ self.boto_associate_address(instance.id, free_public_ip)
+
+ self.debug('Creating IU ubunto repo source list')
+ # choose repo, by defalt, using IU ubuntu repo
+ self.define_repo()
+
+ self.debug('Checking alive instance for deploying')
+ # detect if VMs are ready for deploy
+
+ for instance in self.cloud_instances.get_list().values():
+ if type(instance) is dict:
+ threading.Thread(target=self.installation,
+ args=[instance, 30, True]).start()
+
+ while threading.activeCount() > 1:
+ time.sleep(1)
+
+ self.debug('Configuraing SLURM')
+ # config SLURM system
+ self.config_slurm()
+
+ self.debug('Cleaning up')
+ # clean repo file
+ self.clean_repo()
+ self.debug('Saving cloud instance into backup file')
+ # save cloud instance
+ self.cloud_instances.save_instances()
+ self.debug('Done creation of cluster')
+
+[docs] def clean_repo(self):
+ '''
+ Remove source list file
+
+ No parameters:
+
+ Logic:
+ If if_default flag is set to false, then delete
+ source list file
+
+ No returns
+ '''
+
+ if not self.if_default:
+ self.debug('Removing %s' % self.sources_list)
+ os.remove(self.sources_list)
+
+[docs] def config_slurm(self, create_key=True):
+ '''
+ Configures slurm
+
+ Parameters:
+ create_key -- indicates whether to create munge-key
+ default: true, creates key
+
+ Logic:
+ Reads slurm configuration input file, substitutes controlMachine
+ with control machine id. Append each computation node into
+ configuration file in COMPUTE NODES section
+ Generates munge-key on control node if flag is set to true
+ Does configuration on each node in parallel
+ After all threads are done, removes temp files
+
+ No returns
+ '''
+
+ slurm_conf_file = 'slurm.conf'
+ munge_key_file = 'munge.key'
+
+ self.debug('Opening %s' % self.slurm)
+ self.msg('\nConfiguring slurm.conf')
+ with open(os.path.expanduser(self.slurm)) as srcf:
+ input_content = srcf.readlines()
+ srcf.close()
+
+ self.debug('Getting control machie id')
+ # set control machine
+ controlMachine = self.cloud_instances.get_by_id(0)['id']
+ output = ''.join(input_content) % vars()
+
+ self.msg('\nControl node %s' % controlMachine)
+
+ self.debug('Writting into %s' % slurm_conf_file)
+ # write control machine into slurm.conf file
+ destf = open(slurm_conf_file, 'w')
+ print >> destf, output
+ destf.close()
+
+ self.debug('Openning %s for adding computation nodes'
+ % slurm_conf_file)
+ # add compute machines to slurm conf file
+ with open(slurm_conf_file, 'a') as conf:
+ for instance in self.cloud_instances.get_list().values():
+ if type(instance) is dict:
+ if not instance['id'] == controlMachine:
+ self.debug('Adding instance %s' % instance['id'])
+ conf.write('NodeName=%s Procs=1 State=UNKNOWN\n'
+ % instance['id'])
+ conf.write('PartitionName=debug Nodes=%s Default=YES'
+ ' MaxTime=INFINITE State=UP\n'
+ % instance['id'])
+ conf.close()
+
+ # if needs to create munge key
+ if create_key:
+ self.msg('\nGenerating munge-key')
+
+ # generate munge-key on control node
+
+ self.execute(self.cloud_instances.get_by_id(0),
+ 'sudo /usr/sbin/create-munge-key')
+ self.debug('Opening %s for writting munge-key' % munge_key_file)
+ munge_key = open(munge_key_file, 'w')
+ print >> munge_key, \
+ self.get_command_result("ssh -i %s %s@%s"
+ " 'sudo cat /etc/munge/munge.key'"
+ % (self.userkey,
+ self.user_login,
+ self.cloud_instances.get_by_id(0)['ip'
+ ]))
+ munge_key.close()
+
+ # copy SLURM conf file to every node
+ for instance in self.cloud_instances.get_list().values():
+ if type(instance) is dict:
+ self.debug('Starting SLURM on %s' % instance['id'])
+ process_thread = threading.Thread(target=self.start_slurm,
+ args=[instance,
+ create_key,
+ slurm_conf_file,
+ munge_key_file])
+ process_thread.start()
+
+ # wait all threads are done
+ self.debug('Waitting all threads are done')
+ while threading.activeCount() > 1:
+ time.sleep(1)
+
+ # clean temp files
+ if create_key:
+ self.debug('Removing %s' % munge_key_file)
+ os.remove(munge_key_file)
+ self.debug('Removing %s' % slurm_conf_file)
+ os.remove(slurm_conf_file)
+
+[docs] def start_slurm(self,
+ instance,
+ create_key,
+ slurm_conf_file,
+ munge_key_file):
+ '''
+ Copies SLURM configuration file and munge key file
+ to every node, and start slurm on each node
+
+ Parameters:
+ instance -- cloud instance
+ create_key -- flag indicates whether needs to create munge key
+ slurm_conf_file -- SLURM configuration file name
+ munge_key_file -- munge key file name
+
+ Logic:
+ Copies SLURM configuration file to each node in cluster.
+ If needs to create munge key, then creates munge key on
+ control node, copies it to each node in cluster. Starts
+ SLURM and munge after copy is done
+
+ No returns
+ '''
+
+ # copy slurm.conf
+ self.msg('\nCopying slurm.conf to node %s' % instance['id'])
+ self.copyto(instance, slurm_conf_file)
+ self.execute(instance, 'sudo cp slurm.conf /etc/slurm-llnl')
+
+ # copy munge key
+ if create_key:
+ self.msg('\nCopying munge-key to node %s' % instance['id'])
+ self.copyto(instance, munge_key_file)
+ self.execute(instance,
+ 'sudo cp munge.key /etc/munge/munge.key')
+ self.execute(instance,
+ 'sudo chown munge /etc/munge/munge.key')
+ self.execute(instance,
+ 'sudo chgrp munge /etc/munge/munge.key')
+ self.execute(instance,
+ 'sudo chmod 400 /etc/munge/munge.key')
+
+ # start slurm and munge daemon
+ self.msg('\nStarting slurm on node %s' % instance['id'])
+ self.execute(instance, 'sudo /etc/init.d/slurm-llnl start')
+ self.execute(instance, 'sudo /etc/init.d/munge start')
+
+[docs] def define_repo(self):
+ '''
+ Set ubuntu repo to IU ubuntu repository
+
+ No parameters
+
+ Source list file content:
+ -------------------------
+
+ deb http://ftp.ussg.iu.edu/linux/ubuntu/ natty-updates main
+ deb-src http://ftp.ussg.iu.edu/linux/ubuntu/ natty-updates main
+ deb http://ftp.ussg.iu.edu/linux/ubuntu/ natty universe
+ deb-src http://ftp.ussg.iu.edu/linux/ubuntu/ natty universe
+ deb http://ftp.ussg.iu.edu/linux/ubuntu/ natty-updates universe
+ deb-src http://ftp.ussg.iu.edu/linux/ubuntu/ natty-updates universe
+ deb http://ftp.ussg.iu.edu/linux/ubuntu/ natty main
+ deb-src http://ftp.ussg.iu.edu/linux/ubuntu/ natty main
+
+ Logic:
+ If uses IU ubuntu repository, creates sources_list file
+ which has the former content
+
+ No returns
+ '''
+
+ iu_repo = 'http://ftp.ussg.iu.edu/linux/ubuntu/'
+
+ if self.if_default:
+ self.msg('\nUsing default repository')
+ else:
+ self.msg('\nUsing IU ubuntu repository')
+ self.debug('Using %s' % iu_repo)
+ self.debug('Opening %s for writting' % self.sources_list)
+ with open(self.sources_list, 'w') as source:
+ source.write('deb ' + iu_repo + ' natty-updates main\n')
+ source.write('deb-src ' + iu_repo + ' natty-updates main\n')
+ source.write('deb ' + iu_repo + ' natty universe\n')
+ source.write('deb-src ' + iu_repo + ' natty universe\n')
+ source.write('deb ' + iu_repo + ' natty-updates universe\n')
+ source.write('deb-src ' + iu_repo +
+ ' natty-updates universe\n')
+ source.write('deb ' + iu_repo + ' natty main\n')
+ source.write('deb-src ' + iu_repo + ' natty main\n')
+ source.close()
+
+[docs] def deploy_services(self, instance):
+ '''
+ Deploies SLURM and OpenMPI services
+
+ Parameters:
+ instance -- cloud instance
+
+ Logic:
+ If using IU ubuntu repository, then copies source_list
+ to each instance in the cluster, installs SLURM and
+ OpenMPI on the instance
+
+ No returns
+ '''
+
+ self.msg('\nInstalling SLURM system and OpenMPI on %s\n'
+ % instance['ip'])
+
+ if not self.if_default:
+ self.debug('Copying %s to %s' % (self.sources_list,
+ instance['id']))
+ self.copyto(instance, self.sources_list)
+ self.execute(instance, 'sudo cp %s /etc/apt/'
+ % self.sources_list)
+
+ self.debug('Updating on %s' % instance['id'])
+ self.update(instance)
+ # install SLURM
+ self.debug('Installing slrum-llnl on %s' % instance['id'])
+ self.install(instance, 'slurm-llnl')
+ # install OpenMPI
+ self.debug('Installing openmpi on %s' % instance['id'])
+ self.install(instance, "openmpi-bin libopenmpi-dev")
+
+# ---------------------------------------------------------------------
+# METHODS TO SAVE RUNNING VIRTUAL CLUSTER
+# ---------------------------------------------------------------------
+
+[docs] def save_instance(
+ self,
+ kernel_id,
+ ramdisk_id,
+ instance_ip,
+ instance_name,
+ ):
+ '''
+ Saves one instance
+
+ Parameters:
+ kernel_id: image kernel id
+ ramdisk_id: image ramdisk id
+ instance_ip: instance public IP address
+ instance_name: image name
+
+ Logic:
+ Uses euca-bundle-vol to create a bundle of current image on
+ a remote host. Image has the size of 1GB, and stored under /mnt/
+
+ No returns
+ '''
+
+ if kernel_id == None:
+ self.debug("ssh -i %s %s@%s '. ~/.profile;"
+ " sudo euca-bundle-vol -c ${EC2_CERT}"
+ " -k ${EC2_PRIVATE_KEY} -u ${EC2_USER_ID}"
+ " --ec2cert ${EUCALYPTUS_CERT} --no-inherit"
+ " -p %s -s 1024 -d /mnt/'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ instance_name))
+ return self.get_command_result("ssh -i %s %s@%s '. ~/.profile;"
+ " sudo euca-bundle-vol -c ${EC2_CERT}"
+ " -k ${EC2_PRIVATE_KEY} -u ${EC2_USER_ID}"
+ " --ec2cert ${EUCALYPTUS_CERT} --no-inherit"
+ " -p %s -s 1024 -d /mnt/'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ instance_name))
+ elif ramdisk_id == None:
+ self.debug("ssh -i %s %s@%s '. ~/.profile;"
+ " sudo euca-bundle-vol -c ${EC2_CERT}"
+ " -k ${EC2_PRIVATE_KEY} -u ${EC2_USER_ID}"
+ " --ec2cert ${EUCALYPTUS_CERT} --no-inherit"
+ " -p %s -s 1024 -d /mnt/ --kernel %s'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ instance_name,
+ kernel_id))
+ return self.get_command_result("ssh -i %s %s@%s '. ~/.profile;"
+ " sudo euca-bundle-vol -c ${EC2_CERT}"
+ " -k ${EC2_PRIVATE_KEY} -u ${EC2_USER_ID}"
+ " --ec2cert ${EUCALYPTUS_CERT} --no-inherit"
+ " -p %s -s 1024 -d /mnt/ --kernel %s'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ instance_name,
+ kernel_id))
+ else:
+ self.debug("ssh -i %s %s@%s '. ~/.profile;"
+ " sudo euca-bundle-vol -c ${EC2_CERT}"
+ " -k ${EC2_PRIVATE_KEY} -u ${EC2_USER_ID}"
+ " --ec2cert ${EUCALYPTUS_CERT} --no-inherit"
+ " -p %s -s 1024 -d /mnt/ --kernel %s --ramdisk %s'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ instance_name,
+ kernel_id,
+ ramdisk_id))
+ return self.get_command_result("ssh -i %s %s@%s '. ~/.profile;"
+ " sudo euca-bundle-vol -c ${EC2_CERT}"
+ " -k ${EC2_PRIVATE_KEY} -u ${EC2_USER_ID}"
+ " --ec2cert ${EUCALYPTUS_CERT} --no-inherit"
+ " -p %s -s 1024 -d /mnt/ --kernel %s --ramdisk %s'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ instance_name,
+ kernel_id,
+ ramdisk_id))
+
+[docs] def upload_bundle(
+ self,
+ instance_ip,
+ bucket_name,
+ manifest,
+ ):
+ '''
+ Uploads bundle image given manifest
+
+ Parameters:
+ instance_ip -- instance public IP address
+ bucket_name -- bucket name for bundled image
+ manifest -- manifest for bundled image
+
+ Logic:
+ Uses euca-upload-bundle to upload bundle
+
+ No returns
+ '''
+
+ self.debug("ssh -i %s %s@%s '. ~/.profile;"
+ " euca-upload-bundle -b %s -m %s'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ bucket_name,
+ manifest))
+ return self.get_command_result("ssh -i %s %s@%s '. ~/.profile;"
+ " euca-upload-bundle -b %s -m %s'"
+ % (self.userkey,
+ self.user_login,
+ instance_ip,
+ bucket_name,
+ manifest))
+
+[docs] def describe_images(self, image_id):
+ '''
+ Gets image infomation
+
+ Parameters:
+ image_id -- image id
+
+ Logic:
+ Uses euca-describe-images to get all the inforamtion of image
+ given image id
+
+ Return:
+ Command result of euca-describe-images of given image id
+ '''
+
+ self.debug('euca-describe-images %s' % image_id)
+ return self.get_command_result('euca-describe-images %s' % image_id)
+
+[docs] def get_kernel_id(self, image_id):
+ '''
+ Gets kernel id
+
+ Parameters:
+ image_id -- image id
+
+ Logic:
+ Parses the command results returned by describe image method.
+ Given that sometimes kernel id is empty for certain images,
+ checks if the command returens a kernel id before return it
+
+ Return:
+ kernal id -- kernal id is the 8th element of returned command
+ result
+ '''
+
+ command_result = [x for x in
+ self.describe_images(image_id).split()]
+ if len(command_result) >= 8:
+ self.debug('Kernel ID %s' % command_result[7])
+ return command_result[7]
+
+[docs] def get_ramdisk_id(self, image_id):
+ '''
+ Gets ramdisk id
+
+ Parameters:
+ image_id -- image id
+
+ Logic:
+ Parses the command results returned by describe image method.
+ Given that sometimes ramdisk id is empty for certain images,
+ checks if the command returens a ramdisk id before return it
+
+ Return:
+ ramdisk id -- ramdisk id is the 9th element of returned command
+ result
+ '''
+
+ command_result = [x for x in
+ self.describe_images(image_id).split()]
+ if len(command_result) == 9:
+ self.debug("Ramdisk ID %s" % command_result[8])
+ return command_result[8]
+
+[docs] def save_node(
+ self,
+ image_id,
+ instance_ip,
+ bucket_name,
+ image_name,
+ node_type,
+ out_queue
+ ):
+ '''
+ Saves nodem, upload and register
+
+ Parameters:
+ image_id -- image id of instance
+ instance_ip -- instance public IP address
+ bucket_name -- bucket name for bundled image
+ image_name -- image name for bundled image
+ node_type -- control node or compute node
+ out_queue -- queue in which puts output
+
+ Logic:
+ After gets all the necessary infomraion to save bundle a node,
+ then saves the node, uploads the bundle
+
+ Return:
+ bundled image id -- Parses the command result, gets the bundled
+ image id, then returns it
+ '''
+
+ bundled_image_id = {}
+
+ kernel_id = self.get_kernel_id(image_id)
+ self.debug('Kernel ID %s' % kernel_id)
+ ramdisk_id = self.get_ramdisk_id(image_id)
+ self.debug("Ramdisk ID %s" % ramdisk_id)
+ # get manifest from the last unit
+ manifest = [x for x in self.save_instance(kernel_id,
+ ramdisk_id, instance_ip, image_name).split()].pop()
+
+ self.msg('\nManifest generated: %s' % manifest)
+ self.msg('\nUploading bundle')
+
+ # upload image
+ image = [x for x in self.upload_bundle(instance_ip, bucket_name,
+ manifest).split()].pop()
+ self.debug(image)
+ self.msg('\nUploading done')
+ self.msg('\nRegistering image')
+
+ # register image, and return image id
+ bundled_image_id[node_type] = \
+ self.euca_register(image).split('\t')[1].strip()
+ out_queue.put(bundled_image_id)
+
+[docs] def euca_register(self, image):
+ '''register image'''
+
+ return self.get_command_result('euca-register %s' % image)
+
+[docs] def checkpoint_cluster(self, args):
+ '''
+ Method for saving virtual cluster
+
+ Parameters:
+ args -- this method deals with
+ args.name -- virtual cluster name
+ args.controlb -- control node bucket name
+ args.controln -- control node image name
+ args.computeb -- compute node bucket name
+ args.computen -- compute node image name
+
+ Logic;
+ Checks existence before saving virtual cluster
+ Only saves cluster which is running (Because saved and
+ terminated clusters are not currenly running)
+ Only saves control node and one compute node into images
+ Saves new control node image id and new compute node image id
+ into backup file for later restore. Change status to SAVED after
+ the saving. Then terminates cluster before deletes host information
+ after each termination
+
+ No returns
+ '''
+ if self.interface == 'boto':
+ self.msg('UNIMPLEMENTED')
+ sys.exit()
+ if self.cloud == 'eucalyptus':
+ self.msg('bugs')
+ sys.exit()
+
+ self.debug('Checking if %s is existed' % args.name)
+ # check if cluter is existed
+ if self.cloud_instances.if_exist(args.name):
+ # get cluter by name
+ self.debug('Getting cloud instance %s' % args.name)
+ self.cloud_instances.get_cloud_instances_by_name(args.name)
+ # if cluster is down, terminate the program
+ if not self.cloud_instances.if_status(self.cloud_instances.RUN):
+ self.msg('Error in locating virtual cluster %s, not running?'
+ % args.name)
+ sys.exit()
+ else:
+ self.msg('Error in locating virtual cluster %s, not created?'
+ % args.name)
+ sys.exit()
+
+ self.print_section('Saving virtual cluster')
+ self.msg('Virtual cluster name -- %s' % args.name)
+ self.msg('control node bucket -- %s' % args.controlb)
+ self.msg('control node name -- %s' % args.controln)
+ self.msg('compute node bucket -- %s' % args.computeb)
+ self.msg('compute node name -- %s' % args.computen)
+
+ control = self.cloud_instances.get_by_id(0)
+ compute = self.cloud_instances.get_by_id(1)
+ self.debug('Control node %s, compute node %s'
+ % (control, compute))
+
+ # copy necessary files to instances, and source profile
+ for instance in [control, compute]:
+ self.copyto(instance, os.environ['EC2_CERT'])
+ self.copyto(instance, os.environ['EC2_PRIVATE_KEY'])
+ self.copyto(instance, os.environ['EUCALYPTUS_CERT'])
+ self.copyto(instance, self.enrc)
+ self.execute(instance, 'cat %s >> ~/.profile'
+ % self.enrc.split('/')[-1])
+ self.execute(instance, 'source ~/.profile')
+
+ save_queue = Queue.Queue()
+ self.msg('\nSaving control node %s' % control['id'])
+ threading.Thread(target=self.save_node, args=[control['image'],
+ control['ip'],
+ args.controlb,
+ args.controln,
+ 'control',
+ save_queue
+ ]).start()
+ self.msg('\nSaving compute node %s' % compute['id'])
+ threading.Thread(target=self.save_node, args=[compute['image'],
+ compute['ip'],
+ args.computeb,
+ args.computen,
+ 'compute',
+ save_queue
+ ]).start()
+ while threading.activeCount() > 1:
+ time.sleep(1)
+
+ # return values has the format:
+ # {'control':'', 'compute':''}, but not sure the order
+ for bundled_image_id in [save_queue.get(), save_queue.get()]:
+ if 'control' in bundled_image_id:
+ control_node_id = bundled_image_id['control']
+ elif 'compute' in bundled_image_id:
+ compute_node_id = bundled_image_id['compute']
+
+ # get instance type
+ instance_type = control['type']
+ self.debug('Instance type %s' % instance_type)
+ # get compute node number
+ cluster_size = self.cloud_instances.get_cluster_size() - 1
+ self.debug('Number of computation nodes %d' % cluster_size)
+ # copy list for termination
+ temp_instance_list = list(self.cloud_instances.get_list().values())
+
+ # delete old info
+ self.debug('Deleting %s from backup file' % args.name)
+ self.cloud_instances.del_by_name(args.name)
+
+ self.debug('Setting save info')
+ # set saved cloud info, and change status to saved
+ self.cloud_instances.checkpoint_cloud_instances(args.name,
+ control_node_id,
+ compute_node_id,
+ instance_type,
+ cluster_size)
+ # save cluster
+ self.debug('Saving cluster into backup file')
+ self.cloud_instances.save_instances()
+
+ # terminate instances
+ for instance in temp_instance_list:
+ if type(instance) is dict:
+ self.terminate_instance(instance['id'])
+ self.del_known_host(instance['ip'])
+# ---------------------------------------------------------------------
+# METHODS TO RESTORE VIRTUAL CLUSTER
+# ---------------------------------------------------------------------
+
+[docs] def restore_cluster(self, args):
+ '''
+ Method for restoring cluster
+
+ Parameters:
+ args -- this method deals with
+ args.name -- virtual cluster name
+
+ Logic:
+ Loads control node id, compute node id, instance type,
+ cluster size from backup file.
+ Creates cluster of size one using control node id
+ Creates cluster of cluster size using compute node id
+ Associates IP addresses with all created instances
+ Granted cluster was saved before, so no need to install
+ softwares again, but need to configure SLURM accordingly
+ because all host names changed. No need to create mumge-key
+ because munge-key was also saved in each instance during the
+ saving process.
+
+ Change status to RUN in the end, and save this restored cluster
+ infomation into backup file
+
+ No returns
+ '''
+ if self.interface == 'boto':
+ self.msg('UNIMPLEMENTED')
+ sys.exit()
+ if self.cloud == 'eucalyptus':
+ self.msg('bugs')
+ sys.exit()
+
+ control_node_num = 1
+
+ # only restore cluster which is saved
+ self.debug('Checking if %s is existed' % args.name)
+ if self.cloud_instances.if_exist(args.name):
+ self.debug('Getting cloud %s' % args.name)
+ self.cloud_instances.get_cloud_instances_by_name(args.name)
+ if self.cloud_instances.if_status(self.cloud_instances.SAVED):
+ self.debug('Cloud status: %s' % self.cloud_instances.SAVED)
+ # if cluster is saved, delete old cluster, and create a
+ # new cloud instance for deploying
+ control_node_id = self.cloud_instances.get_list()['control']
+ self.debug('control node %s' % control_node_id)
+ compute_node_id = self.cloud_instances.get_list()['compute']
+ self.debug('compute node %s' % compute_node_id)
+ instance_type = self.cloud_instances.get_list()['type']
+ self.debug('instance type %s' % instance_type)
+ cluster_size = self.cloud_instances.get_list()['size']
+ self.debug('cluster size %s' % cluster_size)
+ self.debug('Creating new cloud instance list')
+ self.cloud_instances.clear()
+ self.debug('Deleting old info from backup file')
+ self.cloud_instances.set_cloud_instances_by_name(args.name)
+ else:
+ self.msg('Error in restoring virtual cluster %s, not saved?'
+ % args.name)
+ sys.exit()
+ else:
+ self.msg('Error in locating virtual cluster %s, not created?'
+ % args.name)
+ sys.exit()
+
+ cluster_size = int(cluster_size) + control_node_num
+ self.print_section('Restoring virtual cluster')
+ self.msg('cluster name -- %s' % args.name)
+ self.msg('number of nodes -- %s' % cluster_size)
+ self.msg('instance type -- %s' % instance_type)
+ self.msg('control image -- %s' % control_node_id)
+ self.msg('compute image -- %s' % compute_node_id)
+
+ # run control node
+ self.debug('Creating control node %s' % control_node_id)
+ self.euca_run_instance(self.user, control_node_num,
+ control_node_id, instance_type)
+ # run compute nodes given number
+ self.debug('Creating compute node %s' % compute_node_id)
+ self.euca_run_instance(self.user, cluster_size - control_node_num,
+ compute_node_id, instance_type)
+
+ # get free ip list
+ self.debug('Getting free IP list')
+ ip_lists = self.euca_describe_addresses()
+
+ time.sleep(5)
+
+ self.msg('\nAssociating IPs')
+ for i in range(cluster_size):
+ time.sleep(1)
+ self.debug('Getting cloud from index %d' % i)
+ instance = self.cloud_instances.get_by_id(i)
+ while not self.euca_associate_address(instance, ip_lists[i]):
+ self.msg('Error in associating IP %s with instance %s, '
+ 'trying again'
+ % (ip_lists[i], instance['id']))
+ time.sleep(1)
+
+ # check ssh port but not install
+ self.debug('Checking alive instance for deploying')
+ for instance in self.cloud_instances.get_list().values():
+ if type(instance) is dict:
+ threading.Thread(target=self.installation,
+ args=[instance, 60, False]).start()
+ while threading.activeCount() > 1:
+ time.sleep(1)
+ # cnfig SLURM but not generating munge-keys
+ self.debug('Configuating SLURM')
+ self.config_slurm(False)
+ # set status to run and save
+ self.debug('Setting status to %s' % self.cloud_instances.RUN)
+ self.cloud_instances.set_status(self.cloud_instances.RUN)
+ self.debug('Deleting old cloud instance info')
+ self.cloud_instances.del_by_name(args.name)
+ self.debug('Saving cloud instance info')
+ self.cloud_instances.save_instances()
+
+# ---------------------------------------------------------------------
+# METHODS TO TERMINATE NAD CLEANUP
+# ---------------------------------------------------------------------
+ @classmethod
+[docs] def del_known_host(cls, ip_addr):
+ '''
+ Deletes known host info from ~/.ssh/known_hosts
+
+ Parameter:
+ ip_addr -- IP address
+
+ Logic:
+ Deletes known host information from ~/.ssh/known_hosts
+ in case it shows man-in-middle-attack message when starts
+ different cluster but using the same IP addresses
+
+ No returns
+ '''
+
+ known_hosts = '~/.ssh/known_hosts'
+# self.msg('Deleting host info from known_hosts')
+
+ with open(os.path.expanduser(known_hosts)) as srcf:
+ host_list = srcf.readlines()
+ srcf.close()
+
+ with open(os.path.expanduser(known_hosts), 'w') as destf:
+ for host in host_list:
+ if host.find(ip_addr) < 0:
+ destf.write(host)
+ destf.close()
+
+[docs] def terminate_instance(self, instance_id):
+ '''terminate instance given instance id'''
+
+ self.msg('Terminating instance %s' % instance_id)
+ if self.interface == 'euca2ools':
+ os.system('euca-terminate-instances %s' % instance_id)
+ elif self.interface == 'boto':
+ try:
+ self.ec2_conn.terminate_instances([instance_id])
+ except:
+ self.msg('ERROR: terminat instance %s failed' % instance_id)
+
+[docs] def shut_down(self, args):
+ '''
+ Method for shutting down a cluster
+
+ Parameters:
+ args -- this method deals with
+ args.name -- virtual cluster name
+
+ Logic:
+ Only terminates cloud instance which is not terminated
+ Checking its existence before termination.
+ Delete host info from ~/.ssh/known_hosts after each termination
+ Change status according to following:
+ If current status is SAVED, then does not change status
+ If current status is RUN, then changes it to DOWN
+
+ No returns
+ '''
+
+ # only terminate cluster which is not terminated
+ self.debug('Checking if %s is existed' % args.name)
+ if self.cloud_instances.if_exist(args.name):
+ self.debug('Getting cloud instance %s' % args.name)
+ self.cloud_instances.get_cloud_instances_by_name(args.name)
+ self.debug('Checking cloud status')
+ # check if cloud instance is terminated
+ if self.cloud_instances.if_status(self.cloud_instances.DOWN):
+ self.msg('\nError in terminating cluster %s, already down?'
+ % args.name)
+ sys.exit()
+ else:
+ self.msg('\nError in finding virtual cluster %s, not created?'
+ % args.name)
+ sys.exit()
+
+ for instance in self.cloud_instances.get_list().values():
+ if type(instance) is dict:
+ self.terminate_instance(instance['id'])
+ self.del_known_host(instance['ip'])
+
+ # change status to terminated, and save
+ self.debug('If status is %s' % self.cloud_instances.RUN)
+ if self.cloud_instances.if_status(self.cloud_instances.RUN):
+ self.debug('Setting status to %s' % self.cloud_instances.DOWN)
+ self.cloud_instances.set_status(self.cloud_instances.DOWN)
+ self.debug('Deleting instance old info')
+ self.cloud_instances.del_by_name(args.name)
+ self.debug('Saving instance into backup file')
+ self.cloud_instances.save_instances()
+# ---------------------------------------------------------------------
+# METHODS TO SHOW VIRTUAL CLUSTER(S) STATUS
+# ---------------------------------------------------------------------
+
+[docs] def show_status(self, args):
+ '''
+ Show status of cluster(s)
+
+ Parameters:
+ args -- this method deals with
+ args.name -- virtual cluster name
+
+ Logic:
+ Reads from backup file to load cluster information given
+ cluster name. If cluster name is specified, then after
+ checks its existence, loads it to display. If no cluster
+ name is specified, then loads all cluster instances to
+ display. If no clusters are saved in the backup file, after
+ prints help message, then quits the program
+
+ No returns
+ '''
+
+ if not args.name:
+ # get all cloud intances
+ cloud_set = self.cloud_instances.get_all_cloud_instances()
+ # if no cloud instances created, then prints msg and quits
+ if len(cloud_set) == 0:
+ self.msg('\nYou do not have any virtual clusters')
+ sys.exit()
+ for cloud in cloud_set:
+ self.msg('\n====================================')
+ self.msg('Virtual Cluster %s (status: %s)'
+ % (cloud['name'], cloud['status']))
+ self.msg('====================================')
+ if cloud['status'] == self.cloud_instances.SAVED:
+ self.msg('Control node -- %s, '
+ 'Compute node -- %s, '
+ 'Instance type -- %s, '
+ 'Cluster size -- %s'
+ % (cloud['control'], cloud['compute'],
+ cloud['type'], cloud['size']))
+ else:
+ for index in range(self.cloud_instances.get_cluster_size(
+ cloud)):
+ if index == 0:
+ node_type = 'control node'
+ else:
+ node_type = 'compute node'
+ self.msg('%s:\t%s\t%s\t%s\t%s'
+ % (cloud[index]['id'], node_type,
+ cloud[index]['ip'], cloud[index]['image'],
+ cloud[index]['type']))
+ else:
+ if not self.cloud_instances.if_exist(args.name):
+ self.msg('Error in finding virtual cluster %s, not created.'
+ % args.name)
+ sys.exit()
+ self.cloud_instances.get_cloud_instances_by_name(args.name)
+ self.msg('\n====================================')
+ self.msg('Virtual Cluster %s (status: %s)'
+ % (args.name, self.cloud_instances.get_status()))
+ self.msg('====================================')
+ if self.cloud_instances.get_list()['status'] == \
+ self.cloud_instances.SAVED:
+ self.msg('Control node -- %s, '
+ 'Compute node -- %s, '
+ 'Instance type -- %s, '
+ 'Cluster size -- %s'
+ % (self.cloud_instances.get_list()['control'],
+ self.cloud_instances.get_list()['compute'],
+ self.cloud_instances.get_list()['type'],
+ self.cloud_instances.get_list()['size']))
+ else:
+ for index in range(self.cloud_instances.get_cluster_size()):
+ cloud = self.cloud_instances.get_by_id(index)
+ if index == 0:
+ node_type = 'control node'
+ else:
+ node_type = 'compute node'
+ self.msg('%s:\t%s\t%s\t%s\t%s'
+ % (cloud['id'], node_type,
+ cloud['ip'], cloud['image'],
+ cloud['type']))
+
+# ---------------------------------------------------------------------
+# METHODS TO SHOW VIRTUAL CLUSTER LIST
+# ---------------------------------------------------------------------
+
+[docs] def get_list(self, _args):
+ '''
+ lists all virtual clusters and status
+
+ Parameters:
+ args -- this method deals with
+ None
+
+ Logic:
+ Reads from backup file to load all virtual cluster information
+ If no cloud instance saved, after shows help message, then quits
+ the program
+ '''
+
+ # get all cloud instances lists
+ cloud_set = self.cloud_instances.get_all_cloud_instances()
+ # if no cloud created, then prints msg and quits
+ if len(cloud_set) == 0:
+ self.msg('\nYou do not have any virtual clusters')
+ sys.exit()
+
+ self.msg('\n===============================')
+ self.msg('Virtual Cluster list')
+ self.msg('================================')
+ for cloud in cloud_set:
+ self.msg('%s: %d compute nodes, 1 control node; status: %s'
+ % (cloud['name'],
+ self.cloud_instances.get_cluster_size(cloud) - 1,
+ cloud['status']))
+
+######################################################################
+# MAIN
+######################################################################
+
+
+[docs]def commandline_parser():
+ '''
+ Parses commandline
+
+ Checks if python version is above 2.7
+ '''
+
+ # Check pyhon version
+ if sys.version_info < (2, 7):
+ print "ERROR: you must use python 2.7 or greater"
+ sys.exit(1)
+
+ virtual_cluster = Cluster()
+
+ parser = \
+ argparse.ArgumentParser(description='Virtual'
+ ' cluster management operations',
+ version='0.2.0')
+ parser.add_argument('--file', action='store',
+ help='Specify futuregrid configure file')
+ parser.add_argument('--debug', action='store_true',
+ help='print debug message')
+ parser.add_argument('--default-repository', action='store_true',
+ help='using default software repository')
+ parser.add_argument('--create-key', action='store_true',
+ help='create userkey')
+ parser.add_argument('--interface', action='store',
+ help='choose interface to use')
+ parser.add_argument('--cloud', action='store',
+ help='choose cloud')
+ subparsers = parser.add_subparsers(help='commands')
+
+ # status command
+ status_parser = subparsers.add_parser('status',
+ help='Show virtual cluster status')
+ status_parser.add_argument('-a', '--name', action='store',
+ help='Show status of '
+ 'virtual cluster of given name')
+ status_parser.set_defaults(func=virtual_cluster.show_status)
+
+ # list command
+ list_parser = subparsers.add_parser('list',
+ help='List virtual cluster and status')
+ list_parser.set_defaults(func=virtual_cluster.get_list)
+
+ # run command
+ run_parser = subparsers.add_parser('run',
+ help='Create a virtual cluster')
+ run_parser.add_argument('-a', '--name', action='store',
+ required=True, help='Virtual cluster name')
+ run_parser.add_argument('-n', '--number', action='store',
+ required=True, help='Numbe of compute nodes'
+ )
+ run_parser.add_argument('-t', '--type', action='store',
+ required=True, help='Instance type')
+ run_parser.add_argument('-i', '--image', action='store',
+ required=True, help='Image id')
+ run_parser.set_defaults(func=virtual_cluster.create_cluster)
+
+ # terminate command
+ terminate_parser = subparsers.add_parser('terminate',
+ help='Terminate virtual cluster')
+ terminate_parser.add_argument('-a', '--name', action='store',
+ required=True,
+ help='Virtual cluster name')
+ terminate_parser.set_defaults(func=virtual_cluster.shut_down)
+
+ # checkpoint command
+ checkpoint_parser = subparsers.add_parser('checkpoint',
+ help='Save virtual cluster')
+ checkpoint_parser.add_argument('-a', '--name', action='store',
+ required=True,
+ help='Virtual cluster name')
+ checkpoint_parser.add_argument('-c', '--controlb', action='store',
+ required=True,
+ help='Control node bucket name')
+ checkpoint_parser.add_argument('-t', '--controln', action='store',
+ required=True,
+ help='Control node image name')
+ checkpoint_parser.add_argument('-m', '--computeb', action='store',
+ required=True,
+ help='Compute node bucket name')
+ checkpoint_parser.add_argument('-e', '--computen', action='store',
+ required=True,
+ help='Compute node image name')
+ checkpoint_parser.set_defaults(func=virtual_cluster.checkpoint_cluster)
+
+ # restore command
+ restore_parser = subparsers.add_parser('restore',
+ help='Restore saved virtual cluster')
+ restore_parser.add_argument('-a', '--name', action='store',
+ required=True,
+ help='Virtual cluster name')
+ restore_parser.set_defaults(func=virtual_cluster.restore_cluster)
+
+ args = parser.parse_args()
+
+ # parse config file, if config file is not specified,
+ # then use default file which is ~/.futuregrid/futuregrid.cfg
+ if args.file:
+ virtual_cluster.parse_conf(args.file)
+ else:
+ virtual_cluster.parse_conf()
+
+ # set flags
+ virtual_cluster.set_flag(args)
+ # choose interface
+ virtual_cluster.set_cloud(args.cloud)
+ virtual_cluster.set_interface(args.interface)
+ args.func(args)
+
+if __name__ == '__main__':
+ commandline_parser()
+
+#!/usr/bin/env python
+'''Virtual cluster shell'''
+
+#import argparse
+#import os
+import pprint
+import optparse
+from cmd2 import Cmd
+from cmd2 import make_option
+from cmd2 import options
+#from cmd2 import Cmd2TestCase
+from futuregrid.virtual.cluster.FGCluster import Cluster
+#from FGCluster import Cluster
+import unittest
+import sys
+
+
+[docs]class Shell(Cmd):
+ '''Virtual cluster shell command'''
+ #multilineCommands = ['None']
+ #Cmd.shortcuts.update({'&': 'speak'})
+ #maxrepeats = 3
+ #Cmd.settable.append('maxrepeats')
+
+ pp = pprint.PrettyPrinter(indent=0)
+
+ echo = True
+ timing = True
+ cluster = None
+
+ prompt = "fg> "
+
+ logo = """
+ _____ _ ____ _ _
+ | ___|_ _| |_ _ _ _ __ ___ / ___|_ __(_) __| |
+ | |_ | | | | __| | | | '__/ _ \ | _| '__| |/ _` |
+ | _| | |_| | |_| |_| | | | __/ |_| | | | | (_| |
+ |_| \__,_|\__|\__,_|_| \___|\____|_| |_|\__,_|
+----------------------------------------------------
+ """
+
+ def preloop(self):
+ self.cluster = Cluster()
+ print self.logo
+
+ def postloop(self):
+ print "BYE FORM GREGOR"
+
+ @options([
+ make_option('-f', '--file', type="string",
+ help="cluster name"),
+ make_option('-i', '--interface', type="string",
+ help="interface")
+ ])
+[docs] def do_config(self, args, opts):
+ '''config - add the configuration from a file'''
+ if not opts.file:
+ self.cluster.parse_conf()
+ else:
+ self.cluster.parse_conf()
+ self.cluster.set_interface(opts.interface)
+
+ def do_debug(self, debug=True):
+ print "to do debug"
+
+ @options([
+ make_option('-a', '--name', type="string",
+ help="cluster name"),
+ make_option('-n', '--number', type="int",
+ help="number of computation nodes"),
+ make_option('-t', '--type', type="string",
+ help="instance type"),
+ make_option('-i', '--image', type="string",
+ help="image id"),
+ ])
+
+ @options([
+ make_option('-a', '--name', type="string",
+ help="cluster name"),
+ make_option('-c', '--controlb', type="string",
+ help="control node bucket"),
+ make_option('-t', '--controln', type="string",
+ help="control node image"),
+ make_option('-m', '--computeb', type="string",
+ help="compute node bucket"),
+ make_option('-e', '--computen', type="string",
+ default='not specified',
+ help="compute node image"),
+ ])
+
+ @options([
+ make_option('-a', '--name', type="string",
+ help="cluster name"),
+ ])
+
+ @options([
+ make_option('-a', '--name', type="string",
+ help="cluster name"),
+ ])
+
+ @options([
+ make_option('-a', '--name', type="string",
+ help="cluster name"),
+ ])
+
+ def do_list(self, args):
+ self.cluster.get_list(args)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('-t', '--test',
+ dest='unittests',
+ action='store_true',
+ default=False,
+ help='Run unit test suite')
+ (callopts, callargs) = parser.parse_args()
+ if callopts.unittests:
+ sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main()
+ unittest.main()
+ else:
+ app = Shell()
+ app.cmdloop()
+
+if __name__ == '__main__':
+ main()
+' + _('Hide Search Matches') + '
') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/doc/build/html/_static/down-pressed.png b/doc/build/html/_static/down-pressed.png new file mode 100644 index 0000000..6f7ad78 Binary files /dev/null and b/doc/build/html/_static/down-pressed.png differ diff --git a/doc/build/html/_static/down.png b/doc/build/html/_static/down.png new file mode 100644 index 0000000..3003a88 Binary files /dev/null and b/doc/build/html/_static/down.png differ diff --git a/doc/build/html/_static/file.png b/doc/build/html/_static/file.png new file mode 100644 index 0000000..d18082e Binary files /dev/null and b/doc/build/html/_static/file.png differ diff --git a/doc/source/old/_static/header_bg.png b/doc/build/html/_static/header_bg.png similarity index 100% rename from doc/source/old/_static/header_bg.png rename to doc/build/html/_static/header_bg.png diff --git a/doc/build/html/_static/jquery.js b/doc/build/html/_static/jquery.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/doc/build/html/_static/jquery.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l =0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l ";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q =0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f
0)for(var j=d;j 0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e -1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"+d+">"},F={option:[1,""],legend:[1,""],thead:[1," ","
"],tr:[2,"","
"],td:[3,""],col:[2,"
"," "],area:[1,""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
"," ",""];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e 0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===" "&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/ + + + + + + + + + + + + + + + + + + +
+ + + ++ + + +++ + ++++ + ++Index
+ + +A
++ +
+ + B
++ +
+ + + C
+D
+E
+F
++ +
+ + + G
+I
++ +
+ + + M
++ +
+ + P
++ +
+ + + R
++ +
+ + S
+T
++ +
+ + U
++ + + +
+ + +
\ + Sort by:\ + best rated\ + newest\ + oldest\ +
\ +\ +
Add a comment\ + (markup):
\ +