scripts/cfs_add_node.py | 53 +++++++++++++++++
scripts/cfs_add_volume.py | 79 +++++++++++++++++++++++++
scripts/cfs_utils.py | 118 ++++++++++++++++++++++++++++++++++++++
scripts/cloudfsd.py | 84 ++++++++++++++-------------
scripts/styles/cfgmain.css | 23 +++++++
scripts/styles/provlist.css | 29 +++++++++
scripts/views/add_node_done.html | 7 ++
scripts/views/add_vol_done.html | 7 ++
scripts/views/add_vol_fail.html | 7 ++
scripts/views/cfgmain.html | 16 +++++
scripts/views/cluster.html | 23 +++++++
scripts/views/volumes.html | 54 +++++++++++++++++
scripts/volstart.py | 2
scripts/wwwcfgmain.py | 19 ------
scripts/wwwcfgroot.py | 34 -----------
scripts/wwwconfirmprovision.py | 59 -------------------
scripts/wwwcss.py | 79 -------------------------
scripts/wwwdoinitcluster.py | 59 -------------------
scripts/wwwdoprovision.py | 114 -------------------------------------
scripts/wwwinitcluster.py | 40 -------------
scripts/wwwprovision.py | 119 ---------------------------------------
scripts/wwwroot.py | 10 ---
22 files changed, 462 insertions(+), 573 deletions(-)
New commits:
commit 152e08b73f136a05c26fd90190d0654c9f0993ea
Author: Jeff Darcy <jdarcy(a)redhat.com>
Date: Tue May 10 12:40:29 2011 -0400
Main task was to use routing and templates instead of commingling logic
with presentation, and coalesce common functionality into a library instead
of duplicating it, all for maintainability. Many other fixes/changes have
been rolled in, including:
form handling that actually works (i.e. not parsing stdin)
consistently include self when getting cluster member list
include last brick in each volume
add missing parameters (e.g. brick_in_use could never have worked)
use "mount" and explicit list of valid fs types
don't let glusterd start our daemons, which it will do the wrong way
Still a long way to go, but it's a start.
diff --git a/scripts/cfs_add_node.py b/scripts/cfs_add_node.py
new file mode 100644
index 0000000..497db36
--- /dev/null
+++ b/scripts/cfs_add_node.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+import fileinput
+import re
+import os
+import socket
+import sys
+
+from bottle import request, template
+
+# TBD: move this into a library if we're going to re-use it elsewhere
+# (or remove it entirely since it seems rather pointless)
+def clean_and_run (cmd):
+ clean_cmd = cmd.replace("|", "").replace("&",
"").replace(">", " ")
+ return os.system(clean_cmd + " > /dev/null 2>&1")
+
+def run_common (node_name):
+ host_name = socket.gethostname()
+
+ # derive the "real" node addr, e.g. if the user enters, e.g.,
+ # 192.168.122.55 and that is this IP for 'this' node then when we're
+ # done can_node_addr will either be 192.168.122.55 or 127.0.0.1.
+ # similarly if the user enters, e.g., <
principalnode>.foo.bar.baz.com,
+ # the result will be the same
+ host_addr = socket.gethostbyname(host_name)
+ node_addr = socket.gethostbyname(node_name)
+ can_node_name = socket.gethostbyaddr(node_addr)
+ can_node_addr = socket.gethostbyname(can_node_name[0])
+
+
+ # now we can do the right thing if we're on the principal node or not
+ if node_addr == host_addr or "127.0.0.1" == can_node_addr :
+ sts = os.system("/usr/bin/sudo /sbin/chkconfig --add glusterd; \
+ /usr/bin/sudo /sbin/chkconfig glusterd on; \
+ /usr/bin/sudo /sbin/service glusterd start")
+ else :
+ # Remote actions.
+ pfx = "/usr/bin/sudo /usr/bin/ssh %s " % can_node_addr
+ clean_and_run(pfx+"/sbin/chkconfig --add glusterd")
+ clean_and_run(pfx+"/sbin/chkconfig glusterd on")
+ clean_and_run(pfx+"/sbin/service glusterd start")
+ # Local actions.
+ pfx = "/usr/bin/sudo "
+ clean_and_run(pfx+"/usr/sbin/gluster peer probe %s"%node_name)
+
+ return template("add_node_done.html",node_name=node_name)
+
+def run_www ():
+ node_name = request.forms.get("NODENAME")
+ return run_common(node_name)
+
+if __name__ == "__main__":
+ run_common(sys.argv[1])
diff --git a/scripts/cfs_add_volume.py b/scripts/cfs_add_volume.py
new file mode 100644
index 0000000..a9e7617
--- /dev/null
+++ b/scripts/cfs_add_volume.py
@@ -0,0 +1,79 @@
+
+import re
+import os
+import string
+import sys
+
+from bottle import request, template
+import cfs_utils
+
+def run_common (vname, vtype, vcount, bricks):
+
+ # TBD: all sorts of input-validity checking
+ # TBD: construct sane brick list
+ cmd = "volume create %s" % vname
+ if vtype != "plain":
+ cmd = "%s %s %s" % (cmd, vtype, vcount)
+ cmd = "%s %s" % (cmd, string.join(bricks))
+ sts = cfs_utils.run_cmd("gluster",cmd).wait()
+ if sts:
+ return template("add_vol_fail.html", name=vname,
+ action="gluster", status=sts)
+
+ ## make the cloudfs vol files with cloudfs
+ # TBD: this should go away in favor of generating these at start
+ # time; see big TBD near the end of the function
+ cmd = "init %s /var/lib/glusterd/cloudfs.tenants" % vname
+ for node in cfs_utils.get_members()[1:]:
+ sts = cfs_utils.run_cmd("cloudfs",cmd,host=node).wait()
+ if sts:
+ act = "cloudfs on %s" % node
+ return template("add_vol_fail.html", name=vname,
+ action=act, status=sts)
+
+ ## make the dirs on each node/volume
+ ## first get all the tenants
+ tenants = []
+ tenants.append("junk")
+ tenant_file = open("/var/lib/glusterd/cloudfs.tenants","r")
+ for tenantline in tenant_file:
+ scratch = re.split(' ', tenantline)
+ tenants.append(scratch[0])
+ ## now make the dirs on every volume
+ for b in bricks:
+ scratch = re.split(':', b)
+ for tenant in tenants :
+ node = scratch[0]
+ cmd = "%s/%s" % (scratch[1], tenant)
+ sts = cfs_utils.run_cmd("mkdir",cmd,host=node).wait()
+ if sts:
+ act = "mkdir on %s" % node
+ return template("add_vol_fail.html", name=vname,
+ action=act, status=sts)
+
+ # TBD: generating the cloudfs volfiles (client+server) and starting the
+ # glusterfsd daemons should be part of a separate "start" action, so
+ # that it's done with a tenant list that's as up-to-date as possible.
+ # It should also be done using the cloudfsd fetch/map infrastructure so
+ # that cfs_mount.py/mount.cloudfs can actually work. Doing it as a
+ # hack on top of Gluster's fetching/portmapping mess, even if it seems
+ # to work in some bogus test environment, is a total waste of time.
+ return template("add_vol_done.html",name=vname);
+
+def run_www ():
+ volume_id = request.forms.get("VOLUMEID")
+ volume_type = request.forms.get("TYPE")
+ replica_or_stripe_count = request.forms.get("COUNT")
+ brick_list = []
+ for prop in request.forms.iterkeys():
+ if prop.startswith("VOLUME_"):
+ brick_list.append(prop[7:])
+ return run_common(volume_id,volume_type,replica_or_stripe_count,
+ brick_list)
+
+if __name__ == "__main__":
+ volume_id = sys.argv[1]
+ volume_type = sys.argv[2]
+ replica_or_stripe_count = sys.argv[3]
+ brick_list = sys.argv[4:]
+ run_common(volume_id,volume_type,replica_or_stripe_count,brick_list)
diff --git a/scripts/cfs_utils.py b/scripts/cfs_utils.py
new file mode 100644
index 0000000..09e5ace
--- /dev/null
+++ b/scripts/cfs_utils.py
@@ -0,0 +1,118 @@
+import os
+import socket
+import subprocess
+
+# The list of filesystems that work is much shorter than the list of
+# filesystems that don't support xattrs, already-remote filesystems (e.g. NFS),
+# pseudo-filesystems (e.g. devfs) or other things that won't work for one
+# reason or another.
+good_fs_types = [ "ext2", "ext3", "ext4", "xfs",
"btrfs" ]
+
+# Sudo is kind of pointless if we're already running as root.
+use_sudo = False
+
+# We use the class cache to avoid having to call through the shell to find
+# executables on $PATH every single time. To do this, we use the shell *once*
+# to find the local path and assume all nodes are configured similarly.
+class_cache = {}
+
+def get_path (program):
+ if not class_cache.has_key(program):
+ for dir in os.getenv("PATH").split(":"):
+ maybe = os.path.join(dir,program)
+ if os.access(maybe,os.X_OK):
+ class_cache[program] = maybe
+ break
+ # KeyError is as good as any exception we might throw ourselves.
+ return class_cache[program]
+
+# This lets users change access methods (e.g. pdsh, slurm, some future method
+# that proxies the command through cloudfsd's own secure connection.
+def make_remote (host, cmd):
+ return "%s %s %s" % (get_path("ssh"), host, cmd)
+
+def run_cmd (program, args, host=None, filters=[]):
+ cmd = "%s %s" % (get_path(program), args)
+ for prog2, args2 in filters:
+ cmd = "%s | %s %s" % (cmd, get_path(prog2), args2)
+ if use_sudo:
+ cmd = "%s %s" % cmd
+ if host:
+ cmd = make_remote(host,cmd)
+ if len(filters):
+ print "executing %s using shell" % cmd
+ child = subprocess.Popen(cmd,shell=True,
+ stdout=subprocess.PIPE)
+ else:
+ print "executing %s without shell" % cmd
+ child = subprocess.Popen(cmd.split(" "),shell=False,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ return child
+
+# NB this node is always first in the list
+def get_members ():
+ peer_ips = run_cmd("gluster","peer status",
+ filters=[("grep","Hostname:")]).stdout
+ my_ip = socket.gethostbyname(socket.gethostname())
+ node_list = [socket.gethostbyaddr(my_ip)[0]]
+ # For each line: strip off the trailing newline, split at colon, take
+ # item #1
+ for peer in [line[:-1].split(": ")[1] for line in peer_ips]:
+ node_list.append(peer)
+ return node_list
+
+# A brick list looks something like this:
+# {
+# "vol_A": [ "server1:/bricks/A", "server2:/bricks/A" ],
+# "vol_B": [ "server2:/bricks/B", "server3:/bricks/B: ]
+# }
+def get_bricks ():
+ vol_list = {}
+ brick_list = []
+ for line in run_cmd("gluster","volume info all").stdout:
+ parts = line[:-1].split(": ")
+ if len(parts) != 2:
+ continue
+ if parts[0] == "Volume Name":
+ if brick_list:
+ vol_list[vol_name] = brick_list
+ vol_name = parts[1]
+ brick_list = []
+ elif parts[0].startswith("Brick"):
+ brick_list.append(parts[1])
+ if brick_list:
+ vol_list[vol_name] = brick_list
+ return vol_list
+
+def dir_in_use (brick_list, dir):
+ for vol_bricks in brick_list.itervalues():
+ for brick in vol_bricks:
+ if brick == dir:
+ return True
+ return False
+
+# A mount list looks something like this:
+# {
+# "server1": [ ( "/bricks/A", True ),
+# ( "/some/other/mount", False) ]
+# "server2": [ ( "/bricks/A", True ),
+# ( "/bricks/B", True ) ],
+# "server3": [ ( "/bricks/B", True ),
+# ( "/what/ever", False ) ]
+# }
+# The second part of each tuple is the "in-use" flag, true iff the mount is
+# part of a volume.
+def get_mounts (brick_list):
+ volumes_on_nodes = {}
+ for node_ip in get_members() :
+ scratch = []
+ for line in run_cmd("mount","",host=node_ip).stdout:
+ bits = line.split(" ")
+ if bits[4] not in good_fs_types:
+ continue
+ mount = "%s:%s" % (node_ip, bits[2])
+ scratch.append((bits[2],dir_in_use(brick_list,mount)))
+ volumes_on_nodes[node_ip] = scratch
+ return volumes_on_nodes
+
diff --git a/scripts/cloudfsd.py b/scripts/cloudfsd.py
index f68d76e..e510823 100755
--- a/scripts/cloudfsd.py
+++ b/scripts/cloudfsd.py
@@ -1,19 +1,18 @@
#!/usr/bin/python
-from bottle import route, post, run, request
+from bottle import route, post, run, view, debug
+
+import os
+import socket
+import string
import paths
import volstart
import volstop
import volmap
-import wwwroot
-import wwwcfgroot
-import wwwcfgmain
-import wwwinitcluster
-import wwwdoinitcluster
-import wwwprovision
-import wwwdoprovision
-import wwwconfirmprovision
+import cfs_utils
+import cfs_add_node
+import cfs_add_volume
CLOUDFSD_PORT = 8080
@@ -47,39 +46,39 @@ def list_users():
print "list users"
@route("/")
-def www_root():
- return wwwroot.www_root()
-
@route("/cfg")
-def www_cfgroot():
- return wwwcfgroot.www_cfgroot()
-
@route("/cfgmain")
-def www_cfgmain():
- return wwwcfgmain.www_cfgmain()
-
-@route("/wwwprovision")
-def www_provision():
- return wwwprovision.www_provision()
-
-@post("/wwwconfirmprovision")
-def www_confirmprovision():
- return wwwconfirmprovision.www_confirmprovision(request.body)
-
-@post("/wwwdoprovision")
-def www_doprovision():
- print "www doprovision"
- return wwwdoprovision.www_doprovision(request.body)
-
-@route("/wwwinitcluster")
-def www_initcluster():
- print "www initcluster"
- return wwwinitcluster.www_initcluster()
-
-@post("/wwwdoinitcluster")
-def www_doinitcluster():
- print "www doinitcluster"
- return wwwdoinitcluster.www_doinitcluster()
+(a)view("cfgmain.html")
+def cfg_main():
+ return dict()
+
+@route("/cluster")
+(a)view("cluster.html")
+def show_cluster():
+ # TBD: handle glusterd presence/startup check sanely
+ cfs_utils.run_cmd("chkconfig","--add glusterd")
+ cfs_utils.run_cmd("chkconfig","glusterd on")
+ cfs_utils.run_cmd("service","glusterd start")
+ node_list = cfs_utils.get_members()
+ return dict(node_list=string.join(node_list,"<br />"))
+
+@post("/cluster/add_node")
+def add_node():
+ return cfs_add_node.run_www()
+
+# TBD: implement remove_node
+
+@route("/volumes")
+(a)view("volumes.html")
+def show_volumes():
+ brick_list = cfs_utils.get_bricks()
+ mount_list = cfs_utils.get_mounts(brick_list)
+ # TBD: allow adding arbitrary directories instead of just mountpoints
+ return dict(bricks=brick_list,mounts=mount_list)
+
+@post("/volumes/add_volume")
+def add_volume():
+ return cfs_add_volume.run_www()
@route("/wwwaddtenant")
def www_addtenant():
@@ -93,5 +92,10 @@ def www_doaddtenant():
def www_listtenants():
print "www listtenants"
+@route("/styles/:sheet")
+def get_style (sheet):
+ return file("styles/%s"%sheet,"r")
+
if __name__ == "__main__":
+ debug(True)
run(host='',port=CLOUDFSD_PORT)
diff --git a/scripts/styles/cfgmain.css b/scripts/styles/cfgmain.css
new file mode 100644
index 0000000..364d88b
--- /dev/null
+++ b/scripts/styles/cfgmain.css
@@ -0,0 +1,23 @@
+body {
+ background-color: #fff;
+ color: #000;
+ font-size: 0.9em;
+ font-family: sans-serif,helvetica;
+ margin: 0;
+ padding: 0;
+}
+
+.banner {
+ text-align: center;
+ margin: 0;
+ padding: 0.6em 2em 0.4em;
+ background-color: #900;
+ color: #fff;
+ font-weight: bold;
+ font-size: 1.75em;
+ border-bottom: 2px solid #000;
+}
+
+.content {
+ padding: 1em 5em;
+}
diff --git a/scripts/styles/provlist.css b/scripts/styles/provlist.css
new file mode 100644
index 0000000..70132a4
--- /dev/null
+++ b/scripts/styles/provlist.css
@@ -0,0 +1,29 @@
+.header {
+ float: left;
+ width: 100%;
+ background-color: #f4f4f4;
+}
+
+.wrapper {
+ position: relative;
+ float: left;
+ left: 0.00%;
+ width: 100.00%;
+ background-color: #cccccc;
+}
+
+tr.d0 td {
+ background-color: #CC9999;
+ color: black;
+}
+
+tr.d1 td {
+ background-color: #9999CC;
+ color: black;
+}
+
+.footer {
+ float: left;
+ width: 100%;
+ background-color: #f4f4f4;
+}
diff --git a/scripts/views/add_node_done.html b/scripts/views/add_node_done.html
new file mode 100644
index 0000000..23308e3
--- /dev/null
+++ b/scripts/views/add_node_done.html
@@ -0,0 +1,7 @@
+<html><head>
+<meta http-equiv="pragma" content="no-cache">
+</head><body>
+<p>Node {{node_name}} added.</p>
+<p><a href="/cluster">Back to cluster
configuration</a></p>
+<p><a href="/cfgmain">Back to main menu</a></p>
+</body></html>
diff --git a/scripts/views/add_vol_done.html b/scripts/views/add_vol_done.html
new file mode 100644
index 0000000..d6ddf58
--- /dev/null
+++ b/scripts/views/add_vol_done.html
@@ -0,0 +1,7 @@
+<html><head>
+<meta http-equiv="pragma" content="no-cache">
+</head><body>
+<p>Volume {{name}} created.</p>
+<p><a href="/volumes">Back to volume
configuration</a></p>
+<p><a href="/cfgmain">Back to main menu</a></p>
+</body></html>
diff --git a/scripts/views/add_vol_fail.html b/scripts/views/add_vol_fail.html
new file mode 100644
index 0000000..5ecf1c5
--- /dev/null
+++ b/scripts/views/add_vol_fail.html
@@ -0,0 +1,7 @@
+<html><head>
+<meta http-equiv="pragma" content="no-cache">
+</head><body>
+<p>Creating volume {{name}} failed (stage {{action}}, status
{{status}}).</p>
+<p><a href="/volumes">Back to volume
configuration</a></p>
+<p><a href="/cfgmain">Back to main menu</a></p>
+</body></html>
diff --git a/scripts/views/cfgmain.html b/scripts/views/cfgmain.html
new file mode 100644
index 0000000..3f46b78
--- /dev/null
+++ b/scripts/views/cfgmain.html
@@ -0,0 +1,16 @@
+<html><head>
+<title>_Red Hat CloudFS Configuration_</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
/>
+<meta http-equiv="Pragma" content="no-cache" />
+<link href="/styles/cfgmain.css" rel="stylesheet"
type="text/css" />
+</head><body>
+<div class="banner">
+ <h1>Red Hat CloudFS Configuration Main</h1>
+</div>
+<div class="content">
+ <p><a href="/cluster">Manage Servers</a></p>
+ <p><a href="/volumes">Manage Volumes</a></p>
+ <p><a href="/wwwlisttenants">Manage Tenants</a></p>
+</div>
+</body></html>
+
diff --git a/scripts/views/cluster.html b/scripts/views/cluster.html
new file mode 100644
index 0000000..58e83d2
--- /dev/null
+++ b/scripts/views/cluster.html
@@ -0,0 +1,23 @@
+<html><head>
+<title>Initialize CloudFS Cluster</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
/>
+<meta http-equiv="pragma" content="no-cache">
+<link rel="stylesheet" type="text/css"
href="/styles/cfgmain.css" />
+</head><body>
+<div class="banner">
+ <h1>Initialize CloudFS Cluster</h1>
+</div>
+<div class="content">
+<h2>Cluster Nodes</h2>
+<p>{{!node_list}}</p>
+<br />
+Enter the hostname of a node to add to the cluster
+<form method="post" action="cluster/add_node">
+ Node Name: <input type="text" name="NODENAME" />
+ <input type="submit" name="ACTION" value="Add" />
+</form>
+<br />
+<form method="get" action="/cfgmain">
+ <input type="submit" value="Done" />
+</form>
+</body></html>
diff --git a/scripts/views/volumes.html b/scripts/views/volumes.html
new file mode 100644
index 0000000..11c964c
--- /dev/null
+++ b/scripts/views/volumes.html
@@ -0,0 +1,54 @@
+<html><head>
+<title>_Provision CloudFS Volume_</title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"
/>
+<meta http-equiv="Pragma" content="no-cache" />
+<link href="/styles/cfgmain.css" rel="stylesheet"
type="text/css" />
+<link href="/styles/provlist.css" rel="stylesheet"
type="text/css" />
+</head><body>
+<div class="banner">
+<h1>Provision CloudFS Volume</h1>
+</div>
+<div class="content">
+<h2>Existing Volumes:</h2>
+ %for vol_name, brick_list in bricks.iteritems():
+ <p>{{vol_name}}
+ %for brick in brick_list:
+ <br /> {{brick}}
+ %end
+ </p>
+ %end
+<hr>
+<h2>Provision a Volume From Available Bricks:</h2>
+<form method="post" name="provision"
action="/volumes/add_volume">
+<div class="header"><hr></div>
+<div class="wrapper">
+<table>
+ %color_index = 0
+ %for node, mount_list in mounts.iteritems():
+ %color = "d%d" % color_index
+ %color_index = (color_index + 1) % 2
+ <tr class="{{color}}">
+ <td>{{node}}</td>
+ %for mpath, minuse in mount_list:
+ %full_path = "%s:%s" % (node,mpath)
+ %if minuse:
+ <td><input type="checkbox" name="VOLUME" value=0
disabled />{{mpath}} (in use)</td>
+ %else:
+ <td><input type="checkbox" name="VOLUME_{{full_path}}"
value=0 />{{mpath}}</td>
+ %end
+
+ %end
+ </tr>
+ %end
+</table>
+</div>
+<div class="footer"><hr></div>
+Volume Type: <input type="radio" name="TYPE"
value="plain" checked />Plain
+<input type="radio" name="TYPE" value="replica"
/>Replicated
+<input type="radio" name="TYPE" value="stripe"
/>Striped
+<br>Replica or Stripe count: <input type="text" name="COUNT"
size="2" />
+<br>Volume ID: <input type="text" name="VOLUMEID" />
+<input type="submit" name="PROVISION" value="Provision"
/>
+</form>
+</div>
+</body></html>
diff --git a/scripts/volstart.py b/scripts/volstart.py
index a8f0655..3c699f7 100644
--- a/scripts/volstart.py
+++ b/scripts/volstart.py
@@ -44,6 +44,7 @@ def scan_gfs_volfiles(vol_name):
# family, socktype, proto, canonname, sockaddr
# We extract the sockaddr of the first item, and the IP addr from that
# TBD: handle IPv6, multi-homed hosts, etc.
+ # TBD: skip loopback addresses based on note above
my_addrs = socket.getaddrinfo(my_name, 0)
my_glob = "%s/vols/%s/%s.*.vol" % (paths.gfs_dir, vol_name, vol_name)
for vf in glob.iglob(my_glob):
@@ -138,6 +139,7 @@ def create_tenant_dirs(vol_file):
def vol_start(vol_name):
vol_base = check_volume_directory(vol_name)
users = parse_user_file(vol_name)
+ # TBD: deal with more than one brick on the same server
vf = scan_gfs_volfiles(vol_name)
new_vf = "%s/%s" % (vol_base, os.path.basename(vf))
outfile = open(new_vf, "w")
diff --git a/scripts/wwwcfgmain.py b/scripts/wwwcfgmain.py
deleted file mode 100644
index b577a4a..0000000
--- a/scripts/wwwcfgmain.py
+++ /dev/null
@@ -1,19 +0,0 @@
-
-import wwwcss
-
-def www_cfgmain() :
-
- ret = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"
\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
- ret = ret + "<html><head>"
- ret = ret + "<title>_Red Hat CloudFS Configuration_</title>"
- ret = ret + "<meta http-equiv=\"Content-Type\"
content=\"text/html; charset=UTF-8\" />"
- ret = ret + "<meta http-equiv=\"Pragma\"
content=\"no-cache\" />"
- ret = ret + wwwcss.css
- ret = ret + "</head><body>"
- ret = ret + "<h1>Red Hat CloudFS Configuration Main<h1/>"
- ret = ret + "<p><a href=\"wwwinitcluster\">Initialize
Cluster</a></p>"
- ret = ret + "<p><a href=\"wwwlisttenants\">Tenant
Management</a></p>"
- ret = ret + "<p><a href=\"wwwprovision\">Provision
Storage</a></p>"
- ret = ret + "</body></html>"
- return ret
-
diff --git a/scripts/wwwcfgroot.py b/scripts/wwwcfgroot.py
deleted file mode 100644
index b9faede..0000000
--- a/scripts/wwwcfgroot.py
+++ /dev/null
@@ -1,34 +0,0 @@
-
-import wwwcss
-
-def www_cfgroot() :
-
- ret = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"
\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"
- ret = ret + "<html><head>"
- ret = ret + "<title>_Red Hat CloudFS Configuration_</title>"
- ret = ret + "<meta http-equiv=\"Content-Type\"
content=\"text/html; charset=UTF-8\" />"
- ret = ret + "<meta http-equiv=\"Pragma\"
content=\"no-cache\" />"
- ret = ret + wwwcss.css
- ret = ret + "<meta http-equiv=\"Refresh\" content=\"0;
url=/cfgmain\" />"
- ret = ret + "</head><body>"
- ret = ret + "<h1>Red Hat CloudFS Configuration<h1/>"
-# authentication disabled for now
-# <h2>Sign On<h2/>
-# <form method="post" action="/cgi-bin/authenticate">
-# <p>
-# <strong>Please enter user ID and password:</strong>
-# <br>
-# <strong>User ID</strong>
-# <input type="text" size="20"
name="USERNAME">
-# <strong>Password</strong>
-# <input type="password" size="20"
name="PASSWORD">
-# </p>
-# <p>
-# <input type="submit" name="signon" value="Sign
On">
-# </p>
-# </form>
- ret = ret + "<p>Please follow <a
href=\"/cfgmain\">link</a>!</p>"
- ret = ret + "</body></html>"
-
- return ret
-
diff --git a/scripts/wwwconfirmprovision.py b/scripts/wwwconfirmprovision.py
deleted file mode 100644
index 8b3ccba..0000000
--- a/scripts/wwwconfirmprovision.py
+++ /dev/null
@@ -1,59 +0,0 @@
-
-import fileinput
-import re
-import os
-
-def www_confirmprovision(body):
-
- # f = fileinput.input()
- line = body.readline()
- # fileinput.close()
-
- tokens = re.split('&', line)
-
- volume_id = ""
- volume_type = ""
- replica_or_stripe_count = ""
- command = ""
- volumes = []
-
- # assert PROVISION= will be the last one, thus we may also assert that
- # all options will be extracted before we try to cons up a cmd
- for index in range(len(tokens)) :
- if tokens[index].startswith("TYPE=") :
- scratch = re.split('=', tokens[index])
- volume_type = scratch[1]
- continue
- if tokens[index].startswith("COUNT=") :
- scratch = re.split('=', tokens[index])
- replica_or_stripe_count = scratch[1]
- continue
- elif tokens[index].startswith("VOLUMEID=") :
- scratch = re.split('=', tokens[index])
- volume_id = scratch[1]
- continue
- elif tokens[index].startswith("VOLUME=") :
- scratch = re.split('=', tokens[index])
- volumes.append(scratch[1])
- continue
-
- ret = "Content-type: text/html\n"
- ret = ret + "<html><head>"
- ret = ret + "<meta http-equiv=\"pragma\"
content=\"no-cache\">"
- ret = ret + "<title>Confirm Provision Volume</title>"
- ret = ret + "</head><body>"
- ret = ret + "<h2>Confirm Provision Volume</h2>"
- cmd = "/usr/sbin/gluster volume create " + volume_id
- if volume_type != "plain" :
- cmd = cmd + " " + volume_type + " " + replica_or_stripe_count
- cmd = cmd + " transport tcp"
- for volume in volumes :
- cmd = cmd + " " + volume.replace("%3A",
":").replace("%2F", "/")
-
- ret = ret + "<form method=\"post\" name=\"provision\"
action=\"wwwdoprovision\">"
- ret = ret + "<input type=\"hidden\" name=\"COMMAND\"
value=\"" + cmd + "\" />"
- ret = ret + cmd + " <br><input type=\"submit\"
name=\"PROVISION\" value=\"Confirm\" />"
- ret = ret + "</form>"
- ret = ret + "</body></html>"
- return ret
-
diff --git a/scripts/wwwcss.py b/scripts/wwwcss.py
deleted file mode 100644
index af378a8..0000000
--- a/scripts/wwwcss.py
+++ /dev/null
@@ -1,79 +0,0 @@
-
-css = """<style type=\"text/css\">
-/*<![CDATA[*/
-body {
-background-color: #fff;
-color: #000;
-font-size: 0.9em;
-font-family: sans-serif,helvetica;
-margin: 0;
-padding: 0;
-}
-:link {
-color: #fff;" + +
-}
-:visited {
-color: #fff;
-}
-a:hover {
-color: #f50;
-}
-h1 {
-text-align: center;
-margin: 0;
-padding: 0.6em 2em 0.4em;
-background-color: #900;
-color: #fff;
-font-weight: normal;
-font-size: 1.75em;
-border-bottom: 2px solid #000;
-}
-h1 strong {
-font-weight: bold;
-}
-h2 {
-font-size: 1.1em;
-font-weight: bold;
-}
-hr {
-display: none;
-}
-.content {
-padding: 1em 5em;
-}
-.content-columns {
-/* Setting relative positioning allows for
-absolute positioning for sub-classes */
-position: relative;
-padding-top: 1em;
-}
-.content-column-left {
-/* Value for IE/Win; will be overwritten for other browsers */
-width: 47%;
-padding-right: 3%;
-float: left;
-padding-bottom: 2em;
-}
-.content-column-left hr {
-display: none;
-}
-.content-column-right {
-/* Values for IE/Win; will be overwritten for other browsers */
-width: 47%;
-padding-left: 3%;
-float: left;
-padding-bottom: 2em;
-}
-.content-columns>.content-column-left, .content-columns>.content-column-right {
-/* Non-IE/Win */
-}
-img {
-border: 2px solid #fff;
-padding: 2px;
-margin: 2px;
-}
-a:hover img {
-border: 2px solid #f50;
-}
-/*]]>*/
-</style>"""
diff --git a/scripts/wwwdoinitcluster.py b/scripts/wwwdoinitcluster.py
deleted file mode 100644
index 29526a5..0000000
--- a/scripts/wwwdoinitcluster.py
+++ /dev/null
@@ -1,59 +0,0 @@
-
-import fileinput
-import re
-import os
-import socket
-
-def www_doinitcluster():
-
- f = fileinput.input()
- line = f.readline()
- fileinput.close()
-
- host_name = socket.gethostname()
-
- tokens = re.split('&', line)
- node_name = ""
-
- # all options will be extracted before we try to cons up a cmd
- for index in range(len(tokens)) :
- if tokens[index].startswith("NODENAME=") :
- scratch = re.split('=', tokens[index])
- node_name = scratch[1]
-
- # derive the "real" node addr, e.g. if the user enters, e.g., 192.168.122.55
- # and that is this IP for 'this' node then when we're done can_node_addr
will
- # either be 192.168.122.55 or 127.0.0.1.
- # similarly if the user enters, e.g., <
principalnode>.foo.bar.baz.com, the
- # result will be the same
- host_addr = socket.gethostbyname(host_name)
- node_addr = socket.gethostbyname(node_name)
- can_node_name = socket.gethostbyaddr(node_addr)
- can_node_addr = socket.gethostbyname(can_node_name[0])
-
-
- # now we can do the right thing if we're on the principal node or not
- if node_addr == host_addr or "127.0.0.1" == can_node_addr :
- sts = os.system("/usr/bin/sudo /sbin/chkconfig --add glusterd; /usr/bin/sudo
/sbin/chkconfig glusterd on; /usr/bin/sudo /sbin/service glusterd start")
- else :
- cmd = "/usr/bin/sudo /usr/bin/ssh " + can_node_addr + " /sbin/chkconfig
--add glusterd"
- clean_cmd = cmd.replace("|", "").replace("&",
"").replace(">", "")
- sts = os.system(clean_cmd + " > /dev/null 2>&1");
- cmd = "/usr/bin/sudo /usr/bin/ssh " + can_node_addr + " /sbin/chkconfig
glusterd on"
- clean_cmd = cmd.replace("|", "").replace("&",
"").replace(">", "")
- sts = os.system(clean_cmd + " > /dev/null 2>&1");
- cmd = "/usr/bin/sudo /usr/bin/ssh " + can_node_addr + " /sbin/service
glusterd start"
- clean_cmd = cmd.replace("|", "").replace("&",
"").replace(">", "")
- sts = os.system(clean_cmd + " > /dev/null 2>&1");
- cmd = "/usr/bin/sudo /usr/sbin/gluster peer probe " + node_name
- clean_cmd = cmd.replace("|", "").replace("&",
"").replace(">", "")
- sts = os.system(clean_cmd + " > /dev/null 2>&1");
-
- ret = "Content-type: text/html\n"
- ret = ret + "<html><head>"
- ret = ret + "<meta http-equiv=\"pragma\"
content=\"no-cache\">"
- ret = ret + "<meta http-equiv=\"Refresh\" content=\"0;
url=wwwinitcluster\" />"
- ret = ret + "</head><body>"
- ret = ret + "<p>Please follow <a
href=\"wwwinitcluster\">link</a>!</p>"
- ret = ret + "</body></html>"
- return ret
diff --git a/scripts/wwwdoprovision.py b/scripts/wwwdoprovision.py
deleted file mode 100644
index 6535c50..0000000
--- a/scripts/wwwdoprovision.py
+++ /dev/null
@@ -1,114 +0,0 @@
-
-import fileinput
-import re
-import os
-
-def www_doprovision(body):
-
- # f = fileinput.input()
- line = body.readline()
- # fileinput.close()
-
- tokens = re.split('&', line)
-
- volume_id = ""
- volume_type = ""
- replica_or_stripe_count = ""
- command = ""
- volumes = []
-
- # assert PROVISION= will be the only one, thus we may also assert that
- # all options will be extracted before we try to cons up a cmd
- for index in range(len(tokens)) :
- if tokens[index].startswith("COMMAND=") :
- scratch = re.split('=', tokens[index])
- command = scratch[1]
- continue
-
- ret = "Content-type: text/html\n"
- ret = ret + "<html><head>"
- ret = ret + "<meta http-equiv=\"pragma\"
content=\"no-cache\">"
- ret = ret + "<title>Provisioned Volume</title>"
- ret = ret + "</head><body>"
- ret = ret + "<h2>Provisioned Volume</h2>"
- ## make the underlying volume with gluster
- decode_cmd = command.replace("%3A", ":").replace("%2F",
"/").replace("+", " ")
- clean_cmd = decode_cmd.replace("|", "").replace("&",
"").replace(">", "")
- gluster_sts = os.system("/usr/bin/sudo " + clean_cmd + " > /dev/null
2>&1")
- ## make the cloudfs vol files with cloudfs
- cloudfs_sts = -1
- cmd_tokens = re.split(' ', clean_cmd)
- if gluster_sts != -1 and cmd_tokens[0] == "/usr/sbin/gluster" and
cmd_tokens[1] == "volume" and cmd_tokens[2] == "create" :
- cloudfs_cmd = "/usr/bin/sudo /usr/bin/cloudfs init " + cmd_tokens[3] + "
/var/lib/glusterd/cloudfs.tenants > /dev/null 2>&1"
- cloudfs_sts = os.system(cloudfs_cmd)
-
- ## make the dirs on each node/volume
- if cloudfs_sts != -1 :
- ## first get all the tenants
- tenants = []
- tenants.append("junk")
- for tenantline in fileinput.input("/var/lib/glusterd/cloudfs.tenants") :
- scratch = re.split(' ', tenantline)
- tenants.append(scratch[0])
- fileinput.close()
- ## now make the dirs on every volume
- first_node = 6
- if cmd_tokens[4] != "transport" :
- first_node = first_node + 2
- nodes = []
- for ii in range(first_node, len(cmd_tokens)) :
- scratch = re.split(':', cmd_tokens[ii])
- nodes.append(scratch[0])
- for tenant in tenants :
- mkdir_cmd = "/usr/bin/sudo /usr/bin/ssh " + scratch[0] + " /bin/mkdir
-p " + scratch[1] + "/" + tenant
- clean_cmd = mkdir_cmd.replace("|", "").replace("&",
"").replace(">", "")
- mkdir_sts = os.system(clean_cmd + " > /dev/null 2>&1")
- if mkdir_sts != 0 :
- ret = ret + "<br> fail: " + clean_cmd
- # copy the modified vol files to the peers
- unique_nodes = set(nodes)
- this_ip = ""
- ifconfig_pipe = os.popen("/sbin/ifconfig eth0")
- for line in ifconfig_pipe :
- line = line.lstrip()
- tokens = re.split('[: ]', line)
- if tokens[0] == "inet" and tokens[1] == "addr" :
- this_ip = tokens[2]
- scratch = []
- scratch.append(this_ip)
-
- unique_nodes = unique_nodes.difference(scratch)
- ifconfig_pipe.close()
- # ssh and scp (i.e. pull). could just scp
- # (i.e. push) but then we would have to add
- # scp to sudoers-- (Would like to minimize
- # the number of things added to sudoers
- vol_name = cmd_tokens[3].replace("|",
"").replace("&", "").replace(">",
"")
- for node in unique_nodes :
- scp_cmd = "/usr/bin/sudo /usr/bin/ssh " + node + " 'cd
/var/lib/glusterd/vols/" + vol_name + " && /usr/bin/scp -q -r " +
this_ip + ":/var/lib/glusterd/vols/" + vol_name + "/* .'"
- scp_sts = os.system(scp_cmd)
- if scp_sts != 0 :
- ret = ret + "<br>fail: " + scp_cmd
-
- # now start the volume
- start_cmd = "/usr/bin/sudo /usr/sbin/gluster volume start " + vol_name
- start_sts = os.system(start_cmd + " > /dev/null 2>&1")
- if start_sts != 0 :
- ret = ret + "<br> fail: " + start_cmd
- else :
- ret = ret + "<br> started " + vol_name
-
- # list FUSE volume specs for download
- mkdir_sts = os.system("/usr/bin/sudo /bin/mkdir -p scratch")
- cp_sts = os.system("/usr/bin/sudo /bin/cp /var/lib/cloudfs/vols/" + vol_name
+ "/" + vol_name + "-fuse.vol.* /var/www/html/cfg/scratch/")
- ret = ret + "<hr><br> client/tenant volume files (right-click to
save-as):"
- for tenant in tenants :
- if tenant != "junk" :
- ret = ret + "<br><a
href=\"/cfg/scratch/%s-fuse.vol.%s\">%s</a>" % (vol_name, tenant,
tenant)
- ret = ret + "<hr>"
- ret = ret + "<form method=\"post\"
action=\"cfgmain\">"
- ret = ret + "<input type=\"submit\" name=\"DONE\"
value=\"Done\"></form>"
-
- ret = ret + "</body></html>"
- return ret
-
diff --git a/scripts/wwwinitcluster.py b/scripts/wwwinitcluster.py
deleted file mode 100644
index 2858d6a..0000000
--- a/scripts/wwwinitcluster.py
+++ /dev/null
@@ -1,40 +0,0 @@
-
-import datetime
-import os
-import re
-import string
-import socket
-import sys
-
-def www_initcluster():
-
- hostname = socket.gethostname()
-
- node_ips = []
-
- sts = os.system("/usr/bin/sudo /sbin/chkconfig --add glusterd; /usr/bin/sudo
/sbin/chkconfig glusterd on; /usr/bin/sudo /sbin/service glusterd start")
- peer_ips = os.popen("/usr/bin/sudo /usr/sbin/gluster peer status | /bin/grep
Hostname:")
- for line in peer_ips :
- tokens = re.split(':', line)
- node_ips.append(socket.gethostbyname(string.strip(tokens[1])))
- peer_ips.close()
-
- ret = "Content-type: text/html\n"
- ret = ret + "<html><head>"
- ret = ret + "<meta http-equiv=\"pragma\"
content=\"no-cache\"><title>Initialize CloudFS
Cluster</title>"
- ret = ret + "</head><body>"
- ret = ret + "<h2>Initialize CloudFS Cluster</h2>"
- ret = ret + "<hr>"
- ret = ret + "<h2>Cluster Nodes</h2>"
- for node_ip in node_ips :
- ret = ret + node_ip + "<br>"
- ret = ret + "<hr><br>"
- ret = ret + "Enter the hostname of a node to add to the cluster"
- ret = ret + "<form method=\"post\"
action=\"wwwdoinitcluster\">"
- ret = ret + "Node Name: <input type=\"text\"
name=\"NODENAME\">"
- ret = ret + "<input type=\"submit\" name=\"ADDNODE\"
value=\"Add Node\"></form>"
- ret = ret + "<hr>"
- ret = ret + "<form method=\"post\"
action=\"/cfgmain\">"
- ret = ret + "<input type=\"submit\" name=\"DONE\"
value=\"Done\"></form>"
- ret = ret + "</body></html>"
- return ret
diff --git a/scripts/wwwprovision.py b/scripts/wwwprovision.py
deleted file mode 100644
index 72dada3..0000000
--- a/scripts/wwwprovision.py
+++ /dev/null
@@ -1,119 +0,0 @@
-
-import datetime
-import os
-import re
-import string
-import socket
-import sys
-
-def brick_used(needle, haystack) :
- for vol in haystack :
- for brick in vol :
- if brick == needle :
- return True
- return False
-
-def www_provision() :
-
- hostname = socket.gethostname()
-
- existing_vols = []
- bricks_by_vol = []
- bbv_index = -1
-
- volinfo_pipe = os.popen("/usr/bin/sudo /usr/sbin/gluster volume info all")
- for line in volinfo_pipe :
- line = line.lstrip()
- if line.startswith("Volume") :
- tokens = re.split(':', line)
- if tokens[0].strip() == "Volume Name" :
- existing_vols.append(tokens[1].strip())
- elif line.startswith("Bricks:") :
- bricks = []
- bricks_by_vol.append(bricks)
- bbv_index = bbv_index + 1
- elif line.startswith("Brick") :
- tokens = re.split(' ', line)
- bricks_by_vol[bbv_index].append(tokens[1].strip())
- volinfo_pipe.close()
-
- node_ips = []
-
- ifconfig_pipe = os.popen("/sbin/ifconfig eth0")
- for line in ifconfig_pipe :
- line = line.lstrip()
- tokens = re.split('[: ]', line)
- if tokens[0] == "inet" and tokens[1] == "addr" :
- node_ips.append(tokens[2])
- ifconfig_pipe.close()
-
- peer_ips = os.popen("/usr/bin/sudo /usr/sbin/gluster peer status | /bin/grep
Hostname:")
- for line in peer_ips :
- tokens = re.split(':', line)
- node_ips.append(socket.gethostbyname(string.strip(tokens[1])))
- peer_ips.close()
-
- volumes_on_nodes = []
-
- for node_ip in node_ips :
- # ret = ret +("<p>%s</p>") % (node_ip)
- cmd = "/usr/bin/sudo /usr/bin/ssh " + node_ip + " df -H"
- volumes_on_node = os.popen(cmd)
- scratch = []
- for line in volumes_on_node :
- line = line.rstrip()
- if False == line.startswith("Filesystem") and False ==
line.startswith("/dev/mapper") and False == line.startswith("tmpfs")
and False == line.endswith(" /") and False == line.endswith("/boot")
:
- scratch.append(line)
- volumes_on_node.close()
- volumes_on_nodes.append(scratch)
-
- ret = "Content-type: text/html\n"
- ret = ret + "<html><head><meta http-equiv=\"pragma\"
content=\"no-cache\"><title>Provision CloudFS
Volume</title>"
- ret = ret + "<style type=\"text/css\">"
- ret = ret + ".colmask{ position: relative; overflow: hidden; margin: 0px auto;
width: 100%; background-color: #b4d2f7 }"
- ret = ret + ".header{ float: left; width: 100%; background-color: #f4f4f4}"
- ret = ret + ".wrapper{ position: relative; float: left; left: 0.00%; width:
100.00%; background-color: #cccccc}"
- ret = ret + "tr.d0 td { background-color: #CC9999; color: black; }"
- ret = ret + "tr.d1 td { background-color: #9999CC; color: black; }"
- ret = ret + ".footer{ float: left; width: 100%; background-color: #f4f4f4}"
- ret = ret + "body { border-width: 0px; padding: 0px; margin: 0px; font-size: 90%;
background-color: #e7e7de}"
- ret = ret + "</style></head><body>"
- ret = ret + "<h1>Provision CloudFS Volume</h1>"
- ret = ret + "<h2>Existing Volumes:</h2>"
- bbv_index = 0
- for existing_vol in existing_vols :
- ret = ret + "<p>" + existing_vol + ":"
- for brick in bricks_by_vol[bbv_index] :
- ret = ret + "<br> " + brick
- bbv_index = bbv_index + 1
- ret = ret + "</p>"
- ret = ret + "<hr>"
- ret = ret + "<h2>Provision a Volume From Available Bricks:</h2>"
- ret = ret + "<form method=\"post\" name=\"provision\"
action=\"wwwconfirmprovision\">"
- ret = ret + "<div class=\"header\"><hr></div>"
- ret = ret + "<div class=\"wrapper\">"
- ret = ret + "<table>"
- node_index = 0
- for node_ip in node_ips :
- ret = ret + "<tr class=\"d%d\">" % (node_index % 2)
- ret = ret + "<td>%s</td>" % (node_ip)
- for volumes_on_node in volumes_on_nodes[node_index] :
- tokens = volumes_on_node.rpartition(" ")
- if brick_used(node_ip + ":" + tokens[2], bricks_by_vol) == True :
- ret = ret + "<td><input type=\"checkbox\"
name=\"VOLUME\" value=\"%s:%s\" disabled />%s</td>" %
(node_ip, tokens[2], tokens[2])
- else :
- ret = ret + "<td><input type=\"checkbox\"
name=\"VOLUME\" value=\"%s:%s\" />%s</td>" % (node_ip,
tokens[2], tokens[2])
- ret = ret + "</tr>"
- node_index = node_index + 1
- ret = ret + "</table>"
- ret = ret + "</div>"
- ret = ret + "<div class=\"footer\"><hr></div>"
- ret = ret + "Volume Type: <input type=\"radio\"
name=\"TYPE\" value=\"plain\" checked />Plain"
- ret = ret + "<input type=\"radio\" name=\"TYPE\"
value=\"replica\" />Replicated"
- ret = ret + "<input type=\"radio\" name=\"TYPE\"
value=\"stripe\" />Striped"
- ret = ret + "<br>Replica or Stripe count: <input type=\"text\"
name=\"COUNT\" size=\"2\" />"
- ret = ret + "<br>Volume ID: <input type=\"text\"
name=\"VOLUMEID\" />"
- ret = ret + "<input type=\"submit\" name=\"PROVISION\"
value=\"Provision\" />"
- ret = ret + "</form>"
- ret = ret + "</body></html>"
- return ret
diff --git a/scripts/wwwroot.py b/scripts/wwwroot.py
deleted file mode 100644
index 6e9a910..0000000
--- a/scripts/wwwroot.py
+++ /dev/null
@@ -1,10 +0,0 @@
-
-def www_root() :
- ret = "Content-type: text/html\n"
- ret = ret + "<html><head>"
- ret = ret + "<meta http-equiv=\"pragma\"
content=\"no-cache\">"
- ret = ret + "<meta http-equiv=\"Refresh\" content=\"0;
url=/cfg\" />"
- ret = ret + "</head><body>"
- ret = ret + "<p>Please follow <a
href=\"/cfg\">link</a>!</p>"
- ret = ret + "</body></html>"
- return ret