I'm growing more and more inclined to formally write a webmin module (or virtualmin module?) that handles not just local Apache, but apache across several physical systems.
I might have time to start on this today, but I have some questions (of course!).
The webmin/virtualmin remote API - is it fully aware of Apache's configuration? IE, is there anything in place to push config changes to remote boxes?
Is this something that is already being worked on that I could lend a hand on rather than starting a new one?
My intention is to set the module up where a prerequisite is that the boxes have some form of shared storage (smb/nfs/afs/etc) that allows them to access the hosted files at the same filepath, and that they have the same apache build and config. Webmin simply updates the configs on the fly, keeps track of stop, start, and restart status.
The upshot of this is that there needs to be a load balancer in front of these boxes. I was going to write a wrapper script for ifconfig to handle this, that updates my load balancer, and maybe have webmin updating dummy0. haven't decided yet. Thoughts?
Hi,
This sounds very interesting, ive got a few questions (excuse my newbie ignorance if ive missed something entirely):
- Is the intention here for redundancy? Or are u intending to simply create a managed network?
- If redundancy have you looked into clustering? I.e. www.linux-ha.org
- Some detail on the application would be helpful.
Im very interested in developing a clustered environment similiar to this for my business, which would have multiple, concurrent instances of client accounts.
Regards,
Morgan
Alright. I feel really dumb, but this project isn't dead. I got dragged off into the MySQL balancer mess, and I almost have that fixed up, then I'm going to have to make a trip to Montreal. I'm working on this in what spare time I have. Just FYI.
I didn't have time to get on this today as I'd hoped, but I'll answer your questions regardless. :)
This could be used either for fault tolerance or "clustering" (I prefer the term farm, as these aren't acting as a single cpu instance, but the term cluster gets used so much these days...). In fact, it would be able to do both.
In my situation, I have a couple of load balancers in deployment already. These are fault-tolerant, and I can assign a pool of port 80 systems to forward requests to, the only requirement is that the systems can all respond to those requests more or less identically. The module I'm looking to create allows you to do that from Webmin/Virtualmin.
My question was basically whether I could use the extant API's to handle application state (ie, is the remote apache running? stopped?) and whether there was an extant way to send over updated configs for apache (I'm thinking not).
In the case that there is no way to push configs, I'm thinking of creative rsync use. Haven't decided how to go about that exactly yet.
Hey Tony,
I think you'll be pleasantly surprised by how much of this stuff is available in the API (primarily the Webmin API), and how tightly you can integrate it into the Virtualmin interface.
Here's what I would do, were I embarking on this job:
Create a Virtualmin plugin (Cluster Web Service, or something). A Virtualmin plugin is just a special case of Webmin module...it has a couple of extra hooks, and doesn't have to have a GUI. The extra hooks allow you to add a checkbox on the Create/Edit Virtual Server page, which you could label "Add to web server cluster".
That's the easy part.
Now, to get the other stuff, you'll want to use Webmin RPC to call out to one or more remote servers to add the new domain to the Apache on those systems (every function in apache-lib.pl can be called remotely, so you could mirror the Virtualmin steps on the remote machines). This bit is easy on creation, but gets more difficult on changes (like installing scripts or whatever, where Virtualmin makes changes to the httpd.conf on the master...). Actually, it may be necessary, or at least easier, to make the changes in the core code, now that I think of it, since you'll have roughly identical activity on each machine.
It sounds like you've abstracted away some of the details that make your case a special case that might be easier, so you might use the Cluster Copy Files module to ship off the updated configuration files after any changes and RPC calls to restart Apache. If you don't have to deal with a different IP on each machine, then a simple copy will do the trick. I think, for a general solution, we'll have to address the different IP problem--so the above direct Apache configuration stuff will have to happen.
--
Check out the forum guidelines!
Hi Tony,
I second Joe's suggestion about doing this as a Virtualmin plugin. Basically, it could be done by writing a plugin that adds a new feature, which when enabled would copy the Apache config for the virtual server to all hosts in the cluster.
This copy could be done with scp, or with Webmin's RPC call feature to the Apache module on the remote systems. You can find documentation on writing a plugin at http://www.webmin.com/modules-virtualmin.html .. for your case, you would only need to implement the feature_name, feature_label, feature_setup, feature_delete and feature_modify functions.
Let me know if you have any questions about plugin development..
--
Check out the forum guidelines!
Thank you for the replies. This is likely to be simpler than I thought, thankfully. If all I have to deal with is replicating the configs using the built-in API, then it should be dead simple. The problem I foresee arising is the fact that Virtualmin pretty much insists on creating virtual interfaces locally, or that an IP address that's used in a virtual host actually exist (which usually makes sense, but here not so much).
Not sure how to get over that hurdle, unless, as I said, I create an ifconfig wrapper script that basically lies to Webmin. :P
Oh, and to deal with the "on change" situation, Joe, if I were to use rsync, I could just rsync the config files on apply, or on change, couldn't I?
There is actually an option on the Module Config page that tells Virtualmin not to check or create interfaces itself - instead, it will assume that you are going to set them up manually. It is called 'Bring up virtual interfaces?' under the 'Other server settings' section..
--
Check out the forum guidelines!
Ah, thanks for that. Somehow I'd overlooked it!
(Now the problem is that I need them created, just no locally...but that can be handled from the module!)
Also, we both really ought to learn to stop working. ;)
Wow, I guess it's been a while since I've done this:
manager# perl index.cgi
Undefined subroutine &main::init_config called at ./apache-cluster.pl line 5.
Compilation failed in require at index.cgi line 6.
manager# head index.cgi
#!/usr/bin/perl
# index.cgi
# Main configuration page for the Apache Clustering Module.
require './apache-cluster.pl'; # Import our module functions.
&header($text{'module_title'}, ""); # Display our title and all headings.
print "<hr>n";
manager# head apache-cluster.pl
# apache-cluster.lib
# Common functions for the Virtualmin Apache Clustering Module
do '../web-lib.pl';
&init_config();
any ideas?
Webmin CGIs generally don't work too well when run from the command line .. you should try it from the web instead.
--
Check out the forum guidelines!
Okay. :)
Error - Missing Content-Type Header
Thus the reason I ran it from the command line. :D
Now what?
Hmm .. I presume this module is in a sub-directory under /usr/libexec/webmin (a real directory, not a symlink to elsewhere) ?
--
Check out the forum guidelines!
Yeah. Really confused. : usually I can run from the command line to find the error, but in this case, it more or less is telling me that init_config isn't available. Any way to get miniserv.pl to spit out some useful logging?
I found miniserv.error in /var/webmin, which is nice and all, but um...
[[19/Apr/2007:19:04:24 -0500]] Reloading configuration
[[19/Apr/2007:19:04:31 -0500]] [[208.231.66.99]] /apache-cluster/ : Missing Content-Type Header
[[19/Apr/2007:19:06:04 -0500]] [[208.231.66.99]] /apache-cluster/ : Missing Content-Type Header
[[19/Apr/2007:19:07:13 -0500]] [[208.231.66.99]] /apache-cluster/ : Missing Content-Type Header
[[19/Apr/2007:19:07:16 -0500]] [[208.231.66.99]] /apache-cluster/ : Missing Content-Type Header
[[19/Apr/2007:19:14:24 -0500]] [[208.231.66.99]] /apache-cluster/ : Missing Content-Type Header
That's not useful. I already knew that. :)
Ugh, nevermind.
I didn't have +x on the files in that directory. Don't know why I didn't think of that before. :(
Sorry for being a pest tonight, one last question -
I'm looking at the cluster file copy module, and wondering whether it would be wiser to somehow use Webmin's API and instruct the system to "do the copy right now", or use another method, as I need to somehow delegate replica systems from non-replica systems.
I'm open to ideas. The other thought I had was polling interest from the community here and if there's significant interest in having this done a certain way, open a bounty on it so I dedicate additional labor hours to doing it the way the community would be happy with (as I'm seeing webmin can actually manage user permissions as well, it would actually be possible for webmin to handle the whole thing, presuming that a webmin box were the load balancer too...not my configuration, but certainly feasible).
For the copy, have a look at how the cluster copy module does it - basically, it uses the remote_write function, which can transfer a file from one Webmin server to another via an RCP-over-HTTP request. Of course, this would only work if you have each domain's Apache config in a separate file, which is the default for most Linux distros ..
--
Check out the forum guidelines!
Actually, it isn't default on FreeBSD, but then I reconfigured it to be that way. :)
I guess I should put a check for that - or httpd.conf would get transferred every time.
This is only semi-related, but I have a question, or perhaps a feature request. :)
I'm using this on my index.cgi:
use HTML::Prototype;
$header_html = &header_html();
&header($text{'module_title'}, "", "", "", "", "", "", $header_html);
That subroute basically loads prototype.js and scriptaculous.js which I have nested in apache-cluster/js/:
sub header_html {
$header_html = '
<script type="text/javascript" src="/apache-cluster/js/prototype.js"></script> [script type="text/javascript" src="/apache-cluster/js/scriptaculous.js"></scri$<script type="text/javascript" src="/apache-cluster/js/fastinit.js"></script>
';
return $header_html;
}
The good news is that this works great. :) I can insert code like this:
print '
<div id="grabme"]
[img src="/servers/images/freebsd.gif" alt="" />
[span>Grab me. GRAB ME!!!!</span>
</div>
';
print $prototype->draggable_element('grabme');
It results in a nice draggable tile. The not-so-good - probably shouldn't go bundling a separate copy of prototype and scriptaculous with modules. I know I'm going to be writing at least one other module (Asterisk Provisioning) after this one, and if everyone were doing this, we'd have dozens of copies of the same .js files floating around. Any chance you might consider bundling a copy into Webmin for calling in? If not, it's not big deal. This works fine for me right now. :)
I think I'd prefer to keep the number of bundled modules / scripts in Webmin low for now.. Unless there end up being many many modules using them.
Perhaps a better solution would be for this .js files to be packaged in a Perl module, that can be installed on systems running your Webmin module. That said, the overhead of a few .js files in minimal.
--
Check out the forum guidelines!
That's okay, I came up with another solution - I made a Scriptaculous module. :)
I'd post it, but can't find the instructions for bundling a .wbm. :)
nm, found it. :)
http://www.numbski.com/downloads/scriptaculous.wbm.gz
Very quick and dirty. Basically it installs, then you will want to do a foreign_require to pull it in at the lib file, and at the top of your .cgi file, do a foreign_call to scriptaculous-lib for scriptaculous_headers, store it to a scalar, and put it into &header in the 8th parameter slot.
Probably add a few more functionas such as only having certain libraries load, but hey - this way all modules can share it. All I have to do then is require that module from my other modules.
Yes, that's a good solution too ..
And since this module exists only to provide a library to others, you can add the hidden=1 line to it's module.info file to prevent it appearing in the module lists..
--
Check out the forum guidelines!
I'd actually done that originally, but had issues (unrelated). If you look in module.info, you'll see I have hidden=0. Can be changed though. I'm going to play with it for a few days, see if I wind up needing or using other functions, and I'll toss it out for general consumption then.
In the meantime, I made pretty good progress on the Apache Cluster module today. Going to take a break from it this weekend and see if I can't wrap up a semi-functional version on monday.
I just thought I'd add to this thread that we probably will be integrating some sort of JavaScript utility and widget library in the standard Webmin bundle at some point in the future (and the existing JavaScript functions will get some kind of overhaul or be stripped out, as they aren't in their own namespace and just aren't making use of modern best-practices). We're using extjs in our new AJAX-y theme, and it has quite a lot of good widgets and functions. We're also using yui for some of its utility functions, but that can be abstracted away (extjs for example can select between jquery, prototype or yui for its utility function library).
--
Check out the forum guidelines!
Back to work on this again today. :)
I did wind up adding a few functions to the Scriptaculous module, generate_draggable_icon, generate_editable_text, and generate_drop_receiver. Each does what it suggest. Each one triggered by
&foreign_call("scriptaculous", (function name), %options)
Each one returns a scalar with HTML in it, all the user has to do is print the scalar. :) Should make generating Ajax elements dead simple. I know I want to implement generate_sortable_list at very least still. I can't think of any other interactive elements I would personally use, but hey. :)
Nice ..
By the way, you can also call functions from other modules with more standard Perl syntax of :
scriptalicious::functionname($args);
--
Check out the forum guidelines!
That's helpful to know, thanks! :)
Hey, stupid question.
Does ReadParse() not know how to deal with multiple arguments with the same name? I've tried this using a select multiple, and simply check boxes with name=nodes. I need to be able to submit multiple nodes at the same time (node id's to be exact), and from what I'm reading, the form submission should go like this:
remote_api.cgi?function=add_replicas&nodes=id1id2id3
This isn't happening though. Whatever hte last value for nodes is overwrites the previous, so I get the very last one every time. I've tried doing @nodes = split(//,$in{nodes}), which I'm certain I've been able to do before, but for whatever reason it doesn't work. The value of $in{nodes} is a string and that string in the above example would be id3, rather than id1id2id3. I could always go outside the API and parse STDIN myself, but I'd prefer to use webmin's API and not reinvent the wheel.
Any idea what I might be doing wrong?
Hi Tony,
The ReadParse function has a pretty silly way of handling multiple parameters with the same name - it just puts them into a single value in the %in hash, separated by null bytes (). So if you had a form like :
<input type=checkbox name=foo value=1> One
<input type=checkbox name=foo value=2> Two
You could get the selected checkbox values with :
&ReadParse();
@checked = split(//, $in{'foo'});
--
Check out the forum guidelines!
If you look above, that's precisely what I'm doing, but I only get the last passed value. I'll go ahead and work around this for now and make a note to come back to it later.
Thanks!
Update - it's not ReadParse(), as my grabForm() routine (that I used to use in old cgi scripts) has the same problem. The issue appears to be with HTML::Prototype's use of serialize. It tries to use the convention form.serialize(This), which is a deprecated convention. Ajax doesn't pay attention to form id's and values on submission unless you serialize them first. The way that HTML::Prototype does it is only getting the last value.
http://prototypejs.org/api/form/serialize
Not that it matters to you per se, but I figure if it helps someone else in the future, might as well put it here for searchability. ;)
I am about ready to rip my hair out now. :P
Fixed the form input issue. Values are getting passed to what I have named remote_api.cgi. This doesn't return a full html document, rather just bare http headers, and some text that updates the page in real time. This works as well.
The problem is something stupidly simple that I can't seem to get working.
I make a sub named add_nodes_as_replicas, which takes an array as an argument. I thought I had this working when I did it on the command line, but it has since stopped.
I pass an array with node id's, and it is supposed to either return 0 for success, or a string with an error message. Again, dead simple.
Well...not when I'm involved apparently. :P First thing I do is run a test to make sure I got parameters to begin with, so I did:
if(! @_){
return($text{'no_nodes_for_addition'});
}
else{
It passes that condition and goes into the main routine.
my @nodes_for_addition = @{ $_[[0]] };
if(! @nodes_for_addition){
return ("Some useful debugging thing here");
This is where I get tripped up. @nodes_for_addition isn't getting populated, and I'm afraid it is because of the way I'm passing that array, but I'm not sure. I'm calling it using this syntax: add_nodes_as_replicas(@nodes);
You have to escape the at sign in order to get it to pass at all. If I iterate through @_, I can confirm that $_[[0]] did get populated, and if I attempt to print $_[[0]], I get this:
ARRAY(0x804dd78)
So it is populated with an array (or at least an array reference?), so then I thought I'd get smart and use @{ $_[[0]] }, which again makes sense, but that comes back either undef or null (can't tell which). Logic tells me that it should print out a space-separated list of the node id's. I also tried to get smart and do scalar(@{ $_[[0]]}), hoping it would give me a count of the number of nodes passed, again, it does not. If I go back to the original routing (prior to passing the array to add_nodes_as_replicas) and print @nodes, I get a space separated list, and scalar(@nodes) does indeed give me a count of the number of id's in the array.
Which brings me to my conundrum - am I using poor syntax when passing the array as an argument to add_nodes_as_replicas(@array), or am I trying to reference it incorrectly? Perhaps I should be using a pointer in there? I've even tried @{ $_[[0]] }->[[0]] in an attempt to get at the first element, no luck. :(
Any ideas?
I've figured this much out - without the , I'm passing the array itself. With the backslash, I'm passing an array reference.
In either case, the actual values within the array aren't getting through. If I pass @nodes, attempting to print is null or undef. If I pass @nodes, it's populated with an array reference. Attempting to dereference it gets me null or undefined @{$_[[0]]).
So...yeah. What on earth. I'm passing hashes as params all over the place. I have no idea what I'm doing that is giving me such headaches using an array.
Could you post the function and the call to it? It is probably a simple mismatch between the format passed and expected ..
If you want to pass an array, you can use code like :
my_function(@array);
sub my_function
{
my ($arrayref) = @_;
foreach my $e (@$arrayref) {
print "array element $en";
}
}
--
Check out the forum guidelines!
I will try to in the morning. In the interim, you might be amused to know that my form submission problems were theme-related.
I'm using theme-stressfree, and that calls prototype.js on it's own, plus I call my prototype.js. His is older than mine, and there's no namespace separation in an html document. :P His javascript was breaking mine basically. As soon as I went back to the "Old Webmin" theme, my code started working.
Short term I'm going to hack theme-stressfree a bit more to call my libs, but long-term the idea is getting into my head that maybe a scriptaculous module isn't the right answer so much as an "ajax-toolkit" or "webmin-widgets" one. That way all old stuff continues working, but people could use newer widgets too.
Dunno. Perhaps theme-stressfree is the only one that makes prototype or ajax calls, and I'm only having issues by asking too much. :)
Alrighty. A simple change to theme.pl in the StreeFree theme has me fixed up there. I simply commented out the print statements for prototype.js, effects.js, and controls.js, and instead did my scriptaculous::scriptaculous_headers call. All is well again. :)
I noticed he uses nifty.js as wel, which is the Nifty Corners Cube library.
http://www.html.it/articoli/niftycube/index.html
So if I'm seeing this right, overall we have 3 libraries that get used around here...Prototype/Scriptaculous, Nifty Corners Cube, and yui (I've never looked at this...).
I'm going to focus on my apache-clustering module for now, adding functions to the scriptaculous module as needed, then I'll come back and try to sort out the js functions conundrum. Probably just bundling these all into a widgets module is the best way to go, and then separate function calls, like scriptaculous_headers(), yui_headers(), and nifty_headers(). The catch to that of course is that various widgets will require different headers, so the function docs would have to specify, ie if you wanted to generate_editable_text(), you must have included scriptaculous_headers() in your header statement someplace.
Okay, back to making things work instead of making things pretty. :)
Huh, I just noticed a pretty serious rendering bug in the apache module while using the StressFree theme. The icon table at top doesn't get rendered. The first thing that gets rendered are the words "Existing Virtual Hosts". I've tested in both Firefox and Safari. Looks to be a styelsheet problem, but I haven't been able to nail it down yet.
More of an FYI than anything. I guess the one to make aware is the author, and not you guys. ;)
Ah well...gives me an excuse to let you know that I've made some pretty good progress here. I wound up tooling my own config file format in XML using XML::Twig, which will allow me to store far more information per node. I'm calling nodes in the cluster replicas as to tell them apart from cluster nodes that are not a part of the apache farm.
Anyway, the replias get stored in $module_config_directory/config.xml. Right now the only keys that are defined are config (the root), and server with subkeys id and comment. I know I'm going to add a few more subkeys under config with global options, and probably a few more subkeys under server with per-system options.
I'm doing everything I can to avoid page refreshes within the module, as tasks such as node status updates would require a page refresh per-node, and that's no good. :) For the moment I'm tweaking the add_nodes_as_replicas function so that the replica list gets updated in real time (almost done), and then I need to get the node list to update at the same time so that whatever node I just added is no longer in the list of available nodes, then work out hte reverse, so that when I remove a replica, it updates the list of available nodes, and removes the replica from the replicas list (whee....).
Anyway, these are all just UI rendering issues. I'm more or less done with the read config/write config/update config issues. Once I finish tweaking up the UI (hopefully today?) then I'll start hash in the functions that actually push out updates to the replica servers. The last step after that is to allow the user to optionally define a balancer, so that when new virtual hosts are assigned with dedicated IP's, the module can optionally go out and update whatever balancer mechanism is being used.
Work work work. I haven't done this much coding in a very long time. My fingertips are starting to blister. (I wish I were kidding...)
Just a quick update. I've finished work on the basic UI the module will be using. I think I'm going to make a prereq of rsync on the boxes participating in the cluster. No reason to reinvent the wheel when a superior tool is available, and for free.
So the next order of operations is to trigger the creation of a serial file that goes into apache's config directory side-by-side with httpd.conf (or equivalent). The file will contain a simple timestamp, and perhaps md5's of all relavent config files. I then need to check Virtualmin to see how it handles updating apache, and get it to trigger a remote command call on each cluster box that rsync's the config from the master. The master will be running an rsync share that is locked down to the IP addresses of the cluster nodes (although I would strongly suggest firewalling it, or better, running it on a private interface). A check will run to make sure the serial number is current, and that md5's match up. Presuming all is well, it will then trigger whatever that node's "apply" command is.
This is all still pretty rough around the edges. Hopefully in the next week or so it will mature enough to make a testing release. Amongst the known problems are that it is not 100% localized (lots of plain english strings when I was lazy and didn't put it in the lang/en file and make a call to that instead), lots and lots of commented debug code that I haven't removed for fear of needing it again, a couple of newly created "webmin widgets" (what I think I'll finally name that Scriptaculous module) that are in the base code rather than being out in the the separate module where it belongs, and the fact that I haven't checked as to have to make sure the user has all required perl modules installed and tell them about it (HTML::Prototype and XML::Twigs at the moment).
I'm particularly proud of the errorConsole widget I made. :) It draws a pretty display box on screen, and you can populate it with text on the fly using the JavaScript call errorConsole('your text here); - it puts it into a monotype font, automatically adds line breaks and scrolls the text like a console window. I've thought about adding some color adjustment options and stuff, but for now it works, and looks pretty cool. The only issue I have with it is that in Safari the style pre-wrap isn't recognized, and ignores all line breaks and newlines, so you can send n or[br/> until the cows come home, it still won't break the line. Really weird.
I guess that's it for now. As I said, I'm hoping to have a functional pre-alpha quality module up for testing sometime this next week. If I do as I should, there will actually be two modules getting installed, webmin-widgets, and apache-cluster.
Excellent! I'd love to take a look at this when it is done..
--
Check out the forum guidelines!
Just to give you a quick idea:
http://www.flickr.com/photos/numbski/sets/72157600177038599/
Hey Jamie? I started to make a change in web-lib-funcs.pl what would clean up much of what I'm doing, not break anything backwards, and make for much more intuitive code.
For example, in my module's index.cgi, I have this:
&header($text{'module_title'}, "", "", "", "", "", "", $custom_headers);
What I was thinking was to modify sub header{}. Right now you kind of interpolate checking for values passed with generating the headers. What I would do (and have backed off of, since that's really your baby and not mine), is to instead use static variables and render the headers in one shot. At the top of the routine, check to see whether $_[[0]] is a hash or not. If not, use assignments as you have in the past, ie:
my $title = $_[[0]];
my $image = $_[[1]];
my $help = $_[[2]] if($_[[2]]);
my $config = $_[[3]] if($_[[3]]);
etc.
if $_[[0]] is a hash however, then we can make for much more readable code. I could do the above like this:
&header({
title => $text{'module_title'},
header => $custom_headers,
});
Then in your routine, you assign as such:
my(%config) = $_[[0]]; # for the sake of readability.
my $title = $config{title};
my $image = $config{image};
my $help = $config{help} if($config{help});
and so on.
Dunno. The fact that so many nulls have to be passed in order to pass certain options just doesn't seem clean to me. :) This way at a glance you know precisely what is being passed and why, and doesn't break old code either.
I agree that the header function should use format parameters instead of $_[[X]] .. I just haven't gotten around to fixing it yet :)
If you prefer a hash-based function call, I'd suggest writing your own wrapper around header() instead, so as not to break existing modules and themes.
--
Check out the forum guidelines!
All I was suggesting was maybe doing this:
if(re($_[[0]]) eq 'HASH'){
# Do it that way.
}
else(
# Do it the same way you always have.
}
:D
Oh well, picking nits.
Why don't you try this tool ;)
http://it-tva.net/XCommand
http://84.249.33.172/XCommand
Remember USE IT IN YOUR OWN RISK READ AGREEMENT!