On Jul 5, 2004, at 7:15 PM, Ray Olszewski wrote:
At 04:14 PM 7/5/2004 -0700, Chad Carr wrote:All,
I started to draw this out, but I just simply do not have skills in that area. Some would question my verbal skills as well, \
Not me. At least, not based on this message ... it is exactly what I was asking for youi to clarify. I'm going to walk through the steps below, choosing to focus on a concrete example: using a Web interface to enter a static IP address, and related information, for the external interface. It's a fairly common need, one that users will often have to do as a first thing.
If this exercise works ... I'm actually doing it as I write this reply ... you'll see the point by the end.
but here goes:
Each of the tools (cdb, trig and tmpl) represents a data store.
cdb -> abstract configuration variables
trig -> abstract actions tied to those variables, with package-specific handlers
tmpl -> package-specific data allowing the creation of operational configuration files from abstract configuration data.
The idea is that an interface (of arbitrary complexity) needs only to know the abstract values (configuration parameters and trigger actions), and the packages can make themselves work.
It would go something like this:
1) The user chooses to configure his or her LEAF router via an interface (web, ncurses, etc.) backed by leaf-tools.
OK. He or she connects via a browser to a Weblet-like server. It displays some sort of top-level menu. One choice is something like "Configure Connection to ISP". User chooses that.
2) The interface makes one or more calls to cdb to obtain the current canonical values for a handful of (hopefully related) configuration parameters.
OK. In practice, this sort of setup would have to get the following variables. (Actually, since this is a first configuration, it doesn't really have to get anything, except perhaps the identity of the external interface.) The choice in [] is the default/current setting.
identity of external interface: [eth0]/eth1
MAC address of interface: [aa:bb:cc:dd:ee:ff]
(might use this for cases where the system needs to do MAC-address
spoofing to deal with ISP authentication baloney)
address assignment method: [static]/dhcp/pppoe
IP address: [0.0.0.0]
Netmask: [0.0.0.0] (or maybe /0)
Broadcast: [255.255.255.255]
Gateway: [0.0.0.0]
DNS Server 1: [0.0.0.0]
DNS Server 2: [0.0.0.0]
(could be more DNS servers, but ISPs typically provide 2)
Userid: []
Password: []
(rarely needed with static connections; often needed with PPPoE)
(these next few might not be part of this page)
SMTP server: [] (this would be an FQN, not an IP address)
NTP Server 1:[] (should take an FQN or an IP address)
NTP Server 2:[] (should take an FQN or an IP address)
How does the list of available interfaces (the first item above) get generated? My guess is that for hardware-level issues, LEAF, and any UI for it, should NOT rely on the database. So in this instance, it actually checks for the physical interfaces (eth*) present, makes them available for selection, and in the absence of a prior configuration, queries cdb for a default choice (eth0 for external, eth1 for internal, eth2 if a DMZ option is available). For PPPoE, the system still wants to know the physical interface to use; the ppp0 interface can be invisible to the UI. Only if the system supports dial-up should a ppp0 be an explicit choice for users.
This is a real rat's nest. There are a couple of main problems with interface configuration itself:
1) Unconfigured interfaces tend not to route packets to the proper destination (or accept them actually, even if you can figure out their address). A distinct problem for http-based configuration interfaces.
2) There are several different types of interface configuration that potentially result in dynamic assignment of parameters such as ip address, mask and dns resolvers.
Functionally, LEAF will not be able to bootstrap ethernet interfaces via anything other than a console-based interface until we are off floppy, which is not a goal of this project, from what I have seen. Until we can throw a bunch of modules on the boot media and detect hardware, this is simply not a possibility. Not a limitation (or a responsibility) of the configuration database or any of the infrastructure components.
Theoretically, a pre-configuration utility (as simple as a Makefile or as complex as a Java program) making use of the cdb structure will have a much easier time of building a bootstrapped floppy image for a newbie than without it (modules included!)
We do have a discussion waiting for us on the best way for the system to handle dynamically assigned variables. I change my perspective on this almost every time i think about it. It must be discussed, at length, and soon.
3) Cdb returns these values to the interface as "name=value" pairs that may be sourced via the usual shell script methods.
This part would look roughly like this (from my memory of how LRP did it in the old days):
$IF_EXT=eth0 $MAC_EXT=aa:bb:cc:dd:ee:ff $IP_EXT_STATIC=true $IP_EXT_PPPOE=false $IP_EXT_DHCP=false $IP_EXT_ADDRESS=0.0.0.0 $IP_EXT_NETMASK=0.0.0.0 $IP_EXT_BROADCAST=255.255.255.255 $IP_EXT_GW=0.0.0.0 $IP_EXT_DNS1=0.0.0.0 $IP_EXT_DNS2=0.0.0.0 $IP_EXT_USERID= $IP_EXT_PASSWD= $IP_EXT_SMTP= $IP_EXT_NTP1= $IP_EXT_NTP2=
Fair enough, something like that.
4) The interface draws some sort of user-friendly input device (probably a form of some kind) to display those values and allow the user to alter them.
Right. I assume the Web system would at a minimum support CGI scripts. Javascript is nice for this sort of stuff, but probably beyond LEAF's limited Web-server capacity.
Remember that altering the values might require additional calls to cdb. In the example, the obvious candidate is "identity of external interface" -- if the user changes it from eth0 to eth1, the rest of the $IF_* values might need to be replaced with the cdb values for eth1. (Or maybe not - swapping interfaces is kind of tricky from a UI perspective.)
This entire passage depends on the interface and how it is laid out. Interface switching can easily be implemented as a "wizard" which allows you to select the interface on the first page, then configure the interface on subsequent pages, thus avoiding the hassle of re-reading data while accepting user input, then trying to figure out what to do.
5) The interface accepts the user input, then makes one or more calls to cdb to update the configuration data.
Where does input validation happen? Does the interface confirm that the entries that are supposed to be IP addresses, for example, have the right form? That the IP address, netmask, broadcast address (if entered separaely and not calculated), and gateway values are mutually consistent? Or does the database backend handle any of that?
Okay, good point. There are a couple of ways to do this, depending on how responsive you want the interface to be. One is to take the user's input all the way down the path, then let the success or failure of all interested components be the validator, only committing changes that have passed muster. This is my preferred methodology for a slim system.
Another good technique is to create a middleware interface with abstracted functions to do data validation prior to inserting into cdb. Heavier, but results in a more responsive UI because you don't get halfway down the path before blowing chunks and have to back out all the changes. I am game for either approach, but the middleware approach needs a lot more design and we seem to be short on time...
6) This data is inserted into cdb's backing store (whatever that happens to be!)
What does the qualifier mean here? Are you contemplating storing the config file off system, accessed via NFS, SMB, or something? For now, I'll simply assume that (a) the database is located on the system in a well-known place and (b) it is its own package, so a package backup can be done at this step (assuming a proper floppy is in the drive).
I just meant that you could use discrete filesystem components as cdb does now or collapse everything into one or more flat files, or use sqlite, or whatever...
I would never contemplate storing the config file off system, as I believe that if we design this properly, we will need it to boot. Your a) and b) assumptions are correct, but I would not necessarily do the config package backup at this point, unless the data is pre-validated by the "middleware."
7) The interface "triggers" one or more system events by calling trig with the name of the event and optional parameters.
A plausible set of events for this example is probably: update_interface_config update_dns_config update_routing_table_config update_smtp_config update_ntp_config update_firewall_config
Or am I being too granular at this stage? Possibly you have in mind something more like:
update_interface_config
update the interface table and reconfigure the interface
(if DHCP or PPPoE) update and restart the needed daemon
update the routing table
restart the firewall so it uses the new external values
update_dns_config
update /etc/resolv.conf
update dnscache configuration (and re-HUP?)
update_ntp_config
update the servers list used by ntpdate and re-HUP the app
where the entries under each event are rough sketches of the scripts that event would run.
Even the above is too granular for my taste. In the case noted, the event fired should probably be "interface/reconfig" which would contain scripts dropped in by root (to reconfigure the interface and system files such as resolv.conf), shorewall (to restart), pppoe or dhclient (to reconfigure and restart), dnscache (to reconfigure and restart), ntpd (to reconfigure and restart). The UI doesn't actually have any concept of what happens when it reconfs an interface, it just changes the data in cdb and tells everyone he did it by firing the trigger.
That way if the user wants to drop on another package that needs to restart when an interface is reconfigured, he just puts the package on his floppy (or uploads it via a special screen on the web interface). The package untars, drops its script down in the right place, and magically, gets run on an interface reconf!
8) Trig runs the scripts that have "registered" for that event (by dropping themselves into the proper directory). The sequencing of this execution is controlled via leading index numbers (think SysV init scripts).
I assume the events themselves also run in a controlled order. In the first version above, update_interface_config has to run before update_firewall_config (for example). In the second version, it is probably not important what order the events themselves run in.
I think generally the interface should be laid out in such a way that a single user input submission generates a single system trigger. You are right, however, that if certain pages result in two or more triggers firing, the UI needs to control the sequence. An example: if you put dns parameters such as hostname and domain name on the same page with interface parameters such as ip address, etc. they should really be separate events. Personally, I would not put global params on the same page with interface specific params, but I lead a simple life...
9) Each script decides for itself what needs to be done on each system event. Generally, this will involve reading the new values from cdb, rewriting its configuration files using the new values, then restarting or otherwise re-initializing itself
OK. Now the only way to read values from cdb is as "name=value" pairs, same as in step 3. Right?
Yes, it is technically correct that cdb delivers values by output of name='value' pairs, but the request interface is hierarchical and recursive. It can handle arrays of values quite naturally, and generates the proper val_0='foo', val_1='bar' on the output. You might say:
# cdb get val
And if val is an array, you would see on standard output:
val_0='foo' val_1='bar'
Check out the test-cdb.sh for the full gamut of cdb access syntax. It is pretty rich.
10) If any trigger fails, the cdb is rolled back to a sane state and the trigger is fired again, restoring the system to its previous state.
Here I'm lost. Passive voice is the weakness here. Something has to do the "roll back to a sane state" part, and it's not clear from context what part of the system makes the decision to do so and initiates the action. We have at this point the individual scripts, the trig glue app, and the UI itself all running.
What constitutes "fails" isn't as obvious as it might sound like. Nor is "sane state". I see why you were vague; this is messy and needs serious thinking.
You are reading very closely. I used passive voice because this type of functionality is not contained within any current component. It might be pushed down into cdb, with a sort of checkpoint/commit/rollback capability, but we might decide to wrap cdb with a higher level interface that does more of a transaction-style interface, setting up a session that can be modified at will and doesn't push its changes until a commit is called.
It did not make sense to write anything on that front until we decided how we wanted to address updates. If we do, out of laziness or expediency, decide to apply all changes, try them out, then reboot without committing if something goes awry, then a full-featured transaction interface would go underappreciated.
My preference: we add checkpoint, commit and rollback functions to cdb. The UI calls "cdb checkpoint" prior to modifying anything. Changes are made to the real thing, triggers are fired in turn and checked for proper exit status, and if all goes well, the UI calls "cdb commit" and the user is prompted to backup the system. If things go poorly, the UI calls "cdb rollback" and fires the triggers again. Slower, but maintains absolute continuity of system state. We would have to test to see if the speed was acceptable.
11) The interface displays either an error or success message. On success, the user is presented with a page offering to back up the system to the boot media thus preserving the state of the system across reboots.
My preference would be always to back up the system (or at least the cdb package) as part of a commit. Both approaches have their strengths and their defects ... probably ovbious to anyone interested in this thread ... so just listing the strengths of one and the defects of the other doesn't help much. On balance, I think prompt backup is more familiar to users, especially inexperienced ones, so that's why I favor it.
I prefer to apply the changes and ask the user to commit them explicitly. It is always harder to recover from subtle errors after writing to non-volatile media. If we follow the procedure above, it is closer to the Cisco way of doing things: apply all the changes you want, try them out, and if you bork something really bad, just power cycle the darn thing and you are guaranteed to come up in a consistent state.
If the required backup medium is not present, the interface should alert the user to the omission. Other than that, the interface should probably say something like "Change Complete" or "Change Failed", with some provision for providing extra information in the failure case.
Now, that is what is in my head, but as you can see, there are several points of contention that need to be discussed thoroughly. But, none of the points of contention have anything to do with the design of the backing store model.
Well ... let me raise one. It appears that cdb accepts input in the form name=value, and it delivers output in the form name=value. What benefit is there from it having a different internal storage method?
I think I addressed these concerns above, but even in light of my explanation, you are free to continue to question them. cdb is no different in basic usage to setting variables, and allows more complex usage if the user desires. A big win, in my book. Not to mention, if someday we get really gonzo and decide to write some C code or get perl, ruby, lua or python on a floppy (gonzo!) access to the cdb is just an open() or fopen() away. No parser necessary.
The natural reply (natural in any object-oriented context, anyway) is, in part, what harm is there? In the context of LEAF, the possible harm is added space requirements. The 3 scripts plus a typical cdb data file are small, but with LEAF, every byte counts, so it's reasonable to ask if the proposed approach takes up unneeded space.
Again, in basic usage the space requirements would be slightly larger, but really nothing with the quantity of variables we are talking about (I remember some folks talking XML when we first started this discussion!)
Frankly, it is a very small part of the overall design and, while cdb may not be perfect, it does work, quite well in fact, and it is done. I say we all take a look at it, stamp it with approval or disapproval, and move on to more pressing questions, like when to back up changes and how the hell to write a full-featured templating system in the 92k stripped down /bin/sh from Oxygen!
Despite the aside above, I actually agree with Chad. Aside from the bugfix level, let's move on. That's why I interpolated an extended example above, to help me (and possibly others) think about these interface and higher-level design issues.
This is a good start. Forgive me for continuing my advocacy of cdb (since it appears that I have the stamp, now) but I really do think once folks start playing with it a bit they will start to see possibilities that I didn't even see when I wrote it. I assure you that our future discussions will not concern cdb but the more pressing issues at hand.
Thanks, Chad
-------------------------------------------------------
This SF.Net email sponsored by Black Hat Briefings & Training.
Attend Black Hat Briefings & Training, Las Vegas July 24-29 - digital self defense, top technical experts, no vendor pitches, unmatched networking opportunities. Visit www.blackhat.com
_______________________________________________ leaf-devel mailing list [EMAIL PROTECTED] https://lists.sourceforge.net/lists/listinfo/leaf-devel