ToDo | 3 -
scripts/cfs_add_directory.py | 120 ++++++++++++++++++++++++++++++++++++++++
scripts/cfs_add_volume.py | 36 +++++++-----
scripts/cfs_start_volume.py | 3 +
scripts/cfs_utils.py | 13 ++++
scripts/cloudfsd.py | 15 +++++
scripts/styles/cfgmain.css | 7 +-
scripts/styles/provlist.css | 16 -----
scripts/views/add_dir_done.html | 15 +++++
scripts/views/add_vol_done.html | 8 ++
scripts/views/tenants.html | 2
scripts/views/volumes.html | 18 ++++--
12 files changed, 218 insertions(+), 38 deletions(-)
New commits:
commit 70633f02811345a63e67f6767b1be444248678a3
Author: Jeff Darcy <jdarcy(a)redhat.com>
Date: Thu May 12 12:59:00 2011 -0400
Allow user to add non-mountpoint directories.
diff --git a/ToDo b/ToDo
index e51d2bd..db4cf21 100644
--- a/ToDo
+++ b/ToDo
@@ -1,5 +1,4 @@
= High Priority =
-Use arbitrary directories, not just mountpoints
Create log/pid directories, move port links to /var/run
Interface to remove volumes
Make sure CLI equivalents work
@@ -7,6 +6,7 @@ Make sure CLI equivalents work
= Medium Priority =
SSL
Sanitize volume/tenant names etc. to avoid XSS/injection
+Add/document code to generate brick list for volume creation
Handle IPv6, multi-homed hosts, localhost in scan_gfs_volfiles
Add "bread crumbs" everywhere for navigation
Interface to remove servers
@@ -16,6 +16,7 @@ Sanity checking for volume configs
Handle zero-length tenant list without alice/bob filler
= Low Priority =
+Interface to remove non-mountpoint directories
Import GlusterFS volume to CloudFS
Eliminate clean_and_run in favor of cfs_utils.run_cmd
diff --git a/scripts/cfs_add_directory.py b/scripts/cfs_add_directory.py
new file mode 100644
index 0000000..508f501
--- /dev/null
+++ b/scripts/cfs_add_directory.py
@@ -0,0 +1,120 @@
+
+import os
+import re
+import socket
+import string
+import sys
+import urllib
+import urllib2
+
+from bottle import request, template
+import cfs_utils
+import cfs_paths
+
+c_range_re = re.compile("\[(?P<first>.)-(?P<last>.)\\]")
+n_range_re =
re.compile("\[(?P<first>[1-9][0-9]*)-(?P<last>[1-9][0-9]*)\\]")
+a_range_re = re.compile("\{(?P<stuff>[^}]+)\}")
+
+def expand (path):
+ old_paths = [path]
+ # Keep going until there are no more expansions.
+ while True:
+ new_paths = []
+ n_subs = 0
+ for p in old_paths:
+ m = c_range_re.search(p)
+ if m:
+ pfx = p[:m.start()]
+ sfx = p[m.end():]
+ first = m.group("first")
+ last = m.group("last")
+ cur = first
+ while True:
+ munged = pfx + cur + sfx
+ new_paths.append(munged)
+ if cur == last:
+ break
+ cur = chr(ord(cur)+1)
+ n_subs += 1
+ continue
+ m = n_range_re.search(p)
+ if m:
+ pfx = p[:m.start()]
+ sfx = p[m.end():]
+ first = int(m.group("first"))
+ last = int(m.group("last"))
+ cur = first
+ while True:
+ munged = "%s%d%s" % (pfx, cur, sfx)
+ new_paths.append(munged)
+ if cur == last:
+ break
+ cur += 1
+ n_subs += 1
+ continue
+ m = a_range_re.search(p)
+ if m:
+ pfx = p[:m.start()]
+ sfx = p[m.end():]
+ for alt in m.group("stuff").split(","):
+ munged = pfx + alt + sfx
+ new_paths.append(munged)
+ n_subs += 1
+ continue
+ new_paths.append(p)
+ if not n_subs:
+ return new_paths
+ old_paths = new_paths
+ # Go back and do it again.
+
+def add_local (path):
+ # TBD: all sorts of input-validity checking
+ db_obj = cfs_utils.open_db()
+ # Get the current list of extra paths.
+ paths_by_node = {}
+ for node in [k for k in db_obj.keys() if k.startswith("xp_")]:
+ xp_list = db_obj[k].split(",")
+ if xp_list == [""]:
+ xp_list = []
+ paths_by_node[k[3:]] = set(xp_list)
+ # Add the user's paths.
+ for p in expand(path):
+ parts = string.split(p,":")
+ if len(parts) != 2:
+ return "add_local(%s) rejected %s on %s" %(
+ path, p, socket.gethostname())
+ node, dir = parts
+ if paths_by_node.has_key(node):
+ paths_by_node[node].add(dir)
+ else:
+ paths_by_node[node] = set([dir])
+ # Store back the results.
+ for key, value in paths_by_node.iteritems():
+ db_obj["xp_"+key] = string.join(value,",")
+ return "add_local(%s) OK on %s" % (path, socket.gethostname())
+
+def run_common (path):
+ node_list = cfs_utils.get_members()
+ blob = []
+ for node in node_list:
+ scratch = [node, []]
+ if socket.gethostbyname(node) in cfs_utils.local_addrs:
+ url_obj = [add_local(path)]
+ else:
+ url = "http://%s:%d/volumes/add_dir_local" % (
+ node, cfs_paths.CLOUDFSD_PORT)
+ data = urllib.urlencode([("path",path)])
+ url_obj = urllib2.urlopen(url,data=data)
+ for line in url_obj:
+ scratch[1].append(line)
+ blob.append(scratch)
+ return template("add_dir_done.html",path=path,blob=blob);
+
+def run_www ():
+ path = request.forms.get("path")
+ return run_common(path)
+
+if __name__ == "__main__":
+ path = sys.argv[1]
+ #run_common(tn_name,tn_pw)
+ print add_local(path)
diff --git a/scripts/cfs_add_volume.py b/scripts/cfs_add_volume.py
index 1835b51..4af663b 100644
--- a/scripts/cfs_add_volume.py
+++ b/scripts/cfs_add_volume.py
@@ -1,14 +1,22 @@
import os
import re
+import socket
import string
import sys
+import urllib
+import urllib2
from bottle import request, template
+import cfs_paths
import cfs_utils
-def run_common (vname, vtype, vcount, bricks):
+def add_local (vname):
+ db_obj = cfs_utils.open_db()
+ db_obj["vt_"+vname] = ""
+ return "add_local(%s) OK on %s" % (vname, socket.gethostname())
+def run_common (vname, vtype, vcount, bricks):
# TBD: all sorts of input-validity checking
# TBD: construct sane brick list
cmd = "volume create %s" % vname
@@ -19,18 +27,20 @@ def run_common (vname, vtype, vcount, bricks):
if sts:
return template("add_vol_fail.html", name=vname,
action="gluster", status=sts)
-
- db_obj = cfs_utils.open_db()
- db_obj["vt_"+vname] = ""
-
- # 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);
+ blob = []
+ for node in cfs_utils.get_nodes_for_vol(vname):
+ scratch = [node,[]]
+ if socket.gethostbyname(node) in cfs_utils.local_addrs:
+ url_obj = [add_local(vname)]
+ else:
+ url = "http://%s:%d/volumes/add_vol_local" % (
+ node, cfs_paths.CLOUDFSD_PORT)
+ data = urllib.urlencode([("vname",vname)])
+ url_obj = urllib2.urlopen(url,data=data)
+ for line in url_obj:
+ scratch[1].append(line)
+ blob.append(scratch)
+ return template("add_vol_done.html",name=vname,blob=blob);
def run_www ():
volume_id = request.forms.get("VOLUMEID")
diff --git a/scripts/cfs_start_volume.py b/scripts/cfs_start_volume.py
index 26f0980..c0b8ce7 100644
--- a/scripts/cfs_start_volume.py
+++ b/scripts/cfs_start_volume.py
@@ -122,6 +122,9 @@ def create_tenant_dirs(vol_file):
for opt_dir_line in opt_dir_lines:
tokens = re.split(' ', string.lstrip(opt_dir_line))
path = string.rstrip(tokens[2])
+ ppath = os.path.dirname(path)
+ if not os.path.exists(ppath):
+ os.mkdir(ppath)
if not os.path.exists(path):
os.mkdir(path)
opt_dir_lines.close()
diff --git a/scripts/cfs_utils.py b/scripts/cfs_utils.py
index 94be076..9655b58 100644
--- a/scripts/cfs_utils.py
+++ b/scripts/cfs_utils.py
@@ -128,8 +128,19 @@ def get_mounts (brick_list):
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)))
+ inuse = dir_in_use(brick_list,mount)
+ scratch.append((bits[2],inuse))
volumes_on_nodes[node_ip] = scratch
+ # Add user-defined non-mountpoint directories.
+ db_obj = open_db()
+ for node in [k[3:] for k in db_obj.keys() if k.startswith("xp_")]:
+ dirs = db_obj["xp_"+node].split(",")
+ if dirs == [""]:
+ dirs = []
+ for dir in dirs:
+ mount = "%s:%s" % (node, dir)
+ inuse = dir_in_use(brick_list,mount)
+ volumes_on_nodes[node].append((dir,inuse))
return volumes_on_nodes
# Get the list of nodes that are serving (any part of) a specific volume.
diff --git a/scripts/cloudfsd.py b/scripts/cloudfsd.py
index 99e715a..2cc7259 100755
--- a/scripts/cloudfsd.py
+++ b/scripts/cloudfsd.py
@@ -11,6 +11,7 @@ import cfs_utils
import volmap
import cfs_utils
import cfs_add_node
+import cfs_add_directory
import cfs_add_volume
import cfs_start_volume
import cfs_stop_volume
@@ -49,10 +50,24 @@ def show_volumes():
# TBD: allow adding arbitrary directories instead of just mountpoints
return dict(bricks=brick_list,mounts=mount_list)
+@post("/volumes/add_directory")
+def add_directory ():
+ return cfs_add_directory.run_www()
+
+@post("/volumes/add_dir_local")
+def add_dir_local ():
+ path = request.forms.get("path")
+ return cfs_add_directory.add_local(path)
+
@post("/volumes/add_volume")
def add_volume():
return cfs_add_volume.run_www()
+@post("/volumes/add_vol_local")
+def add_vol_local ():
+ vname = request.forms.get("vname")
+ return cfs_add_volume.add_local(vname)
+
@route("/volumes/:vol_name/start")
def start_volume(vol_name):
return cfs_start_volume.run_www(vol_name)
diff --git a/scripts/styles/cfgmain.css b/scripts/styles/cfgmain.css
index dc79620..82043e8 100644
--- a/scripts/styles/cfgmain.css
+++ b/scripts/styles/cfgmain.css
@@ -25,16 +25,19 @@ body {
table {
border: 2px black solid;
+ background-color: #cccccc;
}
th {
+ text-align: left;
border: 1px black solid;
- padding: 3px;
+ padding: 5px;
background-color: #b0b0ff;
}
td {
+ text-align: left;
border: 1px black solid;
- padding: 2px;
+ padding: 5px;
background-color: #d0d0ff;
}
diff --git a/scripts/styles/provlist.css b/scripts/styles/provlist.css
index 70132a4..2bd771f 100644
--- a/scripts/styles/provlist.css
+++ b/scripts/styles/provlist.css
@@ -1,25 +1,9 @@
-.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 {
diff --git a/scripts/views/add_dir_done.html b/scripts/views/add_dir_done.html
new file mode 100644
index 0000000..122e4e5
--- /dev/null
+++ b/scripts/views/add_dir_done.html
@@ -0,0 +1,15 @@
+<html><head>
+<meta http-equiv="pragma" content="no-cache">
+</head><body>
+<p>Directory {{path}} added.</p>
+%for node, output in blob:
+ <p><b>{{node}}</b></p>
+ <pre>
+ %for line in output:
+ {{line}}
+ %end
+ </pre>
+%end
+<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_done.html b/scripts/views/add_vol_done.html
index d6ddf58..6044fa6 100644
--- a/scripts/views/add_vol_done.html
+++ b/scripts/views/add_vol_done.html
@@ -1,6 +1,14 @@
<html><head>
<meta http-equiv="pragma" content="no-cache">
</head><body>
+%for node, output in blob:
+ <p><b>{{node}}</b></p>
+ <pre>
+ %for line in output:
+ {{line}}
+ %end
+ </pre>
+%end
<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>
diff --git a/scripts/views/tenants.html b/scripts/views/tenants.html
index c55dc0a..6b5d069 100644
--- a/scripts/views/tenants.html
+++ b/scripts/views/tenants.html
@@ -10,7 +10,7 @@
</div>
<div class="content">
<h2>Existing Tenants:</h2>
- <table cellspacing="0">
+ <table>
<tr><th>Name</th><th>Password</th><th></th></tr>
%tn_list = tenants.keys()
%tn_list.sort()
diff --git a/scripts/views/volumes.html b/scripts/views/volumes.html
index 489778f..7595052 100644
--- a/scripts/views/volumes.html
+++ b/scripts/views/volumes.html
@@ -22,10 +22,21 @@
</ul>
%end
<hr>
-<h2>Provision a Volume From Available Bricks:</h2>
+<h2>Add Directories</h2>
+<p>Add non-mountpoint directories here. The format is host:path, with wildcard
support in the following forms:</p>
+<ul>
+<li>character ranges, e.g. server1:/bricks/brick1</li>
+<li>number ranges, e.g. server1:/bricks/brick[9-10]</li>
+<li>alternatives, e.g. {alpha,bravo}:/bricks/brick20</li>
+</ul>
+<p>Expansions are attempted in the order shown, and the results are stored
persistently as alternatives to the server mount points that are used as a
default.</p>
+<form method="post" action="/volumes/add_directory">
+ <input type="text" name="path" size="40" />
+ <input type="submit" value="Add" />
+</form>
+<hr />
+<h2>Provision a Volume From Available Directories:</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():
@@ -45,7 +56,6 @@
</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