Towards the end of last year, at the COs Christmas meal in the sadly-destroyed Khushi’s, Stephen challenged me to produce an python environment for LCFG components. A relatively simple task, you might think, but he threw in the twist that it had to be properly object oriented. This makes it a much trickier prospect, and set me to thinking about what really object-oriented LCFG components might look like.
We’ve got the problem that originally, LCFG was written in sh. Not that great for structured programming, but the original LCFG design still managed to treat what we now know as components as ‘objects’ with methods such as ‘start’ and ‘stop’. The first problem with this is that the execution context wasn’t preserved between each method invocation (components were just scripts which were called with the method as an argument – so every time a method was run the script was invoked from scratch). A simple form of persistence was added – allowing methods to serialise selected object attributes into a file, which would then be loaded, and the attributes reinitialised when the script restarted. This was extended to resources, so that the resource set when ‘stop’ was called would always match that from when the component was initially started. The ‘configure’ method was added to provide a defined mechanism of transitioning between resource sets when the component was running.
All of these additional features were implemented in ‘sh’ – straining the flexibility of the shell to breaking point, and causing numerous restrictions on valid attribute and resource names and values. More recently, we made the jump to implementing some components in perl, but the perl environment is essentially a port of the existing sh one, and adds little in the way of new structure or abstraction.
This leaves a somewhat creaking component environment, which is hamstrung through its implementation language, need for backwards compatibility, and tight coupling with the LCFG client (which handles the interaction between the components and the profiles which the client collects from the LCFG server). If we take a step back from these realities, what would the ideal component framework look like?
Firstly, we need to preserve resource structure when passing sections of the profile to components. Both the current perl and sh frameworks use the collapsed ‘x_y_z’ list form, which makes it impossible to deal with structured resources in meaningful ways. We need to define a new object hierarchy which makes it possible to preserve the structure of the resources in the XML profile right the way through to the component code.
Secondly, we need to deal with the installation, and removal cases. Currently, LCFG has no concept of component installation, or removal. Installation can be detected within the component code as the first time the ‘start’ or ‘configure’ methods get called on a machine, but there’s no generic mechanism for this. There’s currently no way of handling removal – a component never knows when it’s removed from the boot.services list, and so has no way of telling which ‘stop’ invocation is its last. This leads to machines which transition between multiple roles often having a large amount of detritus in their root partitions.
Thirdly, we need to more strictly define persistence. The current persistence definitions are adhoc, partly because sh never gave a clear way of inferring attributes. I believe that our requirements for handling the installation case mean that we should split a component into two different objects. One, Factory-style object should persist from installation to removal. That is, the install method should be that objects constructor, and removal its destructor. All of the objects attributes, and a copy of the resource set, should persist throughout the life of that Factory object, with a ‘configure’ mechanism being available to deal with resource changes throughout its life. For some components, the Factory will be all that is required. For example components which don’t manage daemons, such as pam and sasl, have no meaningful concept of ‘start’ and ‘stop’. Other components will need to have instances which handle the lifecycle of a service. These instances would be created by calling a method of the Factory class, and would have ‘start’ as their constructor, and ‘stop’ as the destructor. In this way, attributes can persist throughout the life of a daemon. In the future, when we support non-singleton components, it would be possible for the Factory to produce multiple concurrent instances.
Fourthly, we need to build things that can be inherited. As Stephen has noted elsewhere, LCFG desperately needs a way of allowing components to inherit from other components. However, handling the resource set implications of this will require server changes. But, there’s an additional kind of inheritance that we should be interested in. Many of our components do similar tasks, and share large chunks of code. To date, the restrictions of our implementation language (for sh) and framework (for perl) has restricted inheriting useful super classes. Whatever new framework we define should make it trivial to, for example, write a class which handles safely starting, stopping, and notifying a daemon, which can then be inherited by all classes requiring that functionality.
So, that’s my ideal world. Comments?