Hello list, I am looking for advice/best-practices on how to handle inter module dependency''s. We have a fairly large/complex code base (100+ modules) with a lot of history (we started at 0.24) and lately we have taken into looking how we can improve the quality of the codebase. Parameterized classes, the style guide are all quick wins and no brainers. But we have some intermodule dependency''s, mostly because of ordering, for which a proper design pattern is more elusive. A good example is our ldap setup, this setup needs to happen after the initialization of our packaging system. It also has to happen before a lot of the other modules, because ldap provides the details for some of the file owners/groups that are used. We have experimented with a few methods of getting this setup, but have always found significant drawbacks. Without stages we tried three ways of doing this: Creating a dependency chain between classes. Class[''Ldap''] -> Class[''Mysql'']. This is very easy to do, but doesn''t work if we inherit from Ldap, say: class ldap::server inherits ldap The ordering between ldap::server and Mysql is not guaranteed. It also requires the maintainer of the ldap module to know about all modules that depend on ldap and update them if he decides to inherit. A task that is likely to be forgotten. Creating a dependency chains between resources in the modules, f.e. notify''s. Every module that is part of an dependency defines an notify{ ''endpoint'': } and makes sure that everything within the module is executed before the notify. If we inherit from the base class, the overriding class is responsible for making sure that endpoint is still the last thing executed in this module. Making it more likely that the ordering of events will remain as we want it after a continued year of development. But because of assumptions about out base image, and the rarity of reinstalls. it is easy to forgot the requirements in modules that actually need them, Leading to some subtle bugs where the first puppet run on a fresh install might not work but subsequent runs do. Luckily execution is now in fixed-order, otherwise that would have been a problem as well. The third is the use of stages for the ordering of actions, but this seems to be an all or nothing approach, and the result is a very splintered module. For example, our packaging setup is quite complex. First we initialise the packaging system and configure all the default package source, then custom sources could be configured on top of that we allow (un)masking of specific package versions. And after all this one can install a package. We could define 4 stages and each module that needs to do one of these actions would need to run classes in the designated stage, this results in some very splintered packages. Or we could define only 2 stages and have the base setup run before everything else and then wrap all other actions with defines that specify the ordering between them using some self-build ordering mechanism based on notify''s or classes. A problem with this would be that those defines could only be used in the main stage, because of the built-in ordering. Modules adding more stages, like ldap, would need to do something custom for installing the required packages, which again makes maintenance of the package module more difficult to do right. So after this rather longer email explaining our problem and some of the options we explored, how do you guys handle these kind of complex inter-module dependencies? Cheers, Jos Houtman -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
Hello, On 12-01-23 04:45 AM, Jos Houtman wrote:> I am looking for advice/best-practices on how to handle inter module > dependency''s. > We have a fairly large/complex code base (100+ modules) with a lot of > history (we started at 0.24) and lately we have taken into looking how > we can improve the quality of the codebase. > Parameterized classes, the style guide are all quick wins and no brainers. > > But we have some intermodule dependency''s, mostly because of ordering, > for which a proper design pattern is more elusive.I''m fairly interested in this subject and would like to see what others have to say here. From my experience, modules tend to come in "sets" of inter-dependant modules, and this makes it sometimes painful to integrate modules from external sources. Even just merging divergant modules can be quite tedious. For example, I worked on merging new stuff that was developed on the nagios module by others in the community.. but since service resources are declared pretty much _everywhere_ in the module code base, it took an extensive analysis of what the changes would impact and the merging process took much more time than it should have because of this.> A good example is our ldap setup, this setup needs to happen after the > initialization of our packaging system. > It also has to happen before a lot of the other modules, because ldap > provides the details for some of the file owners/groups that are used. > [...]In this case, the link between the differring blocks should be externalized from your ldap module (e.g. the ldap module should care about stuff related to ldap.. not about relations to other modules). You could put the order declaration in a "node type" or "node role" kind of class that you include in your node. say: class mysql_server_role { include ldap_authentication_role # which declares whatever is needed # for ldap support include mysql Class[''Ldap''] -> Class[''Mysql''] } -- Gabriel Filion -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
> > > In this case, the link between the differring blocks should be > externalized from your ldap module (e.g. the ldap module should care > about stuff related to ldap.. not about relations to other modules). > > You could put the order declaration in a "node type" or "node role" kind > of class that you include in your node. > say: > > class mysql_server_role { > include ldap_authentication_role # which declares whatever is needed > # for ldap support > include mysql > > Class[''Ldap''] -> Class[''Mysql''] > } >I see your point and we allready have this role setup in place. But also maintaining ordering declarations for every module that we include is gonna be a painfull, especially if you have, as we, widespread use of common defines that also impose their own ordering. A system simular to stages, but with the ability to assign stages to more then just a class This would allow me to do the following: define portage_useflag_override() { File{ "": stage => useflag } } define hyves_package($category) { package{ "": stage => package } } Class portage { exec {"emerge --sync": } } Class ldap { hyves_package{''nss-switch'': } notify{"done ldap": } } Stage[''portage-setup''] -> Stage[''useflag''] -> Stage[''package''] -> Stage[''ldap''] -> Stage[''main''] Class{"portage": stage => portage-setup; } Class{"ldap": stage => ldap; } portage_useflag_override{"test":} And have an execution order like this one: exec[''emerge --sync''] -> portage_useflag_override["test"] -> hyves_package["nss-switch"] -> notify["done ldap"] The two foremost gains with such an approach would be that cross-module ordering could worked out using stages. While the few modules that deliver common services used by other modules, through defines, can work without complicated, possibly cross-stage, dependency chains. Regards, Jos and not dependency chains that would need to cross stages.> > -- > Gabriel Filion >-- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
On Mon, Jan 23, 2012 at 1:45 AM, Jos Houtman <jos@hyves.nl> wrote:> I am looking for advice/best-practices on how to handle inter module dependency''s.In general, I try and think of module dependencies and organization as a matter of composition. Discrete modules themselves should avoid establishing relationships with other modules. A module should, however, be diligent about managing the internal relationships of the classes and resources it defines. Ideally, Puppet itself would be more opinionated about the relationship of modules, and we''re moving in that direction. Kelsey Alexander is working with Matt Robinson on things like #11979 [1] which gets us on the path to managing dependencies automatically. In the meantime, I document and leave it up to the end user to create another class that composes modules together. With your example, I''d do something like this as the end user. As the module author, I''d try and write mysql and ldap as if the other didn''t exist. node www1 { include site::dbserver } # <modulepath>/site/manifests/dbserver.pp # This is what it means to be a database at the Acme.com site. class site::dbserver { class { ldap: } -> class { mysql: } } [snip]> Without stages we tried three ways of doing this: > Creating a dependency chain between classes. > Class[''Ldap''] -> Class[''Mysql'']. > This is very easy to do, but doesn''t work if we inherit from Ldap, say: > class ldap::server inherits ldapIn this situation I''d update the composition in the site module. I''d also avoid inheritance if at all possible, but that''s another story for another thread. node www1 { include site::dbserver2 } # <modulepath>/site/manifests/dbserver2.pp # This is what it means to be a database at the Acme.com site. class site::dbserver2 { class { ''ldap::server'': } -> class { mysql: } }> The ordering between ldap::server and Mysql is not guaranteed. > It also requires the maintainer of the ldap module to know about all modules > that depend on ldap and update them if he decides to inherit. A task that is > likely to be forgotten.For these reasons I try and think of the whole thing as a composition problem. The things being composed, modules, should try and avoid knowing implementation details about each other.> Creating a dependency chains between resources in the modules, f.e. > notify''s. > Every module that is part of an dependency defines an notify{ ''endpoint'': } > and makes sure that everything within the module is executed before the > notify.This sounds a lot like the Anchor pattern [2]. Are you trying to accomplish the same goal of classes encapsulating other classes in the dependency graph?> If we inherit from the base class, the overriding class is responsible for > making sure that endpoint is still the last thing executed in this module. > Making it more likely that the ordering of events will remain as we want it > after a continued year of development.Yep, really sounds like the anchor pattern [2].> So after this rather longer email explaining our problem and some of the > options we explored, how do you guys handle these kind of complex > inter-module dependencies?Not easily. =) The anchor pattern is a bug, certainly, and so is the UX of having to manage dependencies so carefully and painfully. The good news is that we''re actively working on it. If you could update any of the following bugs with your user stories and desires it will help shape the solution. 11832, 12243, 12246, 12249, 12250, 12251, 12253, 12255, 12256, 12257, 12258, 12259, 12260, [1] http://projects.puppetlabs.com/issues/11979 [2] http://projects.puppetlabs.com/projects/puppet/wiki/Anchor_Pattern -- Jeff McCune -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
On Jan 23, 3:45 am, Jos Houtman <j...@hyves.nl> wrote:> Hello list, > > I am looking for advice/best-practices on how to handle inter module > dependency''s.[...]> Without stages we tried three ways of doing this: > Creating a dependency chain between classes. > Class[''Ldap''] -> Class[''Mysql'']. > This is very easy to do, but doesn''t work if we inherit from Ldap, say: > class ldap::server inherits ldap > The ordering between ldap::server and Mysql is not guaranteed. > It also requires the maintainer of the ldap module to know about all > modules that depend on ldap and update them if he decides to inherit. A > task that is likely to be forgotten.That''s one of many reasons to not do that. Specifically, one should employ class inheritance only when it involves overriding resource properties of the parent class. Where all you really want is to compose classes (as I infer is the case you describe), you should instead use the ''include'' function, the ''require'' function, or a resource-style class declaration. If you use class inheritance *only* to override resource properties (subclass bodies contain nothing but resource overrides) then I think your subclasses will not suffer from the problem you describe. That''s a more restrictive rule than I impose on myself, but it might be useful to you. You will already have recognized that none of this solves your problem directly. Effective dependency management requires discipline, planning, documentation, and the occasional large-scale refactoring. Correct use of class inheritance fits in mostly by shaping the expectations and practices of manifest developers, rather than by providing tools to directly control ordering. I hope you nevertheless find this useful. John -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
> In general, I try and think of module dependencies and organization as > a matter of composition. Discrete modules themselves should avoid > establishing relationships with other modules. A module should, > however, be diligent about managing the internal relationships of the > classes and resources it defines. > > Ideally, Puppet itself would be more opinionated about the > relationship of modules, and we''re moving in that direction. Kelsey > Alexander is working with Matt Robinson on things like #11979 [1] > which gets us on the path to managing dependencies automatically. In > the meantime, I document and leave it up to the end user to create > another class that composes modules together. With your example, I''d > do something like this as the end user. As the module author, I''d try > and write mysql and ldap as if the other didn''t exist.I would love doing that and the proper way forward with this seems to me to be stages. Which leads me to the question, when does one use stages and when to use dependency''s between classes. Stages look like they have great potention, but there use seems to be lacking. My stab at an answer would be: Dependency between classes should be used within modules while stages should manage the ordering between modules.> # <modulepath>/site/manifests/dbserver.pp > # This is what it means to be a database at the Acme.com site. > class site::dbserver { > class { ldap: } > -> class { mysql: } > > }In our case, without stages, This could easily grow to an dependency chain of 20 to 30 classes.> > In this situation I''d update the composition in the site module. I''d > also avoid inheritance if at all possible, but that''s another story > for another thread.Agreed, to avoiding inheritence.> For these reasons I try and think of the whole thing as a composition > problem. The things being composed, modules, should try and avoid > knowing implementation details about each other.In general I agree with this, but I do think it would be usefull if a module can expose functionality to other classes through defines. The main reason for this is locality, mysql needs the firewall rule so it defines the firewall rule. This improves the self-documenting nature of the code, gives a clear overview of what the options are when you want a mysql daemon (firewall, performance-monitoring, alerting, maintenance crontabs) through an parameterized mysql class, and simplifies the node definitions. But this practice disagree''s with the use of stages or would require something like a staged_definition, a definition that can be called from any stage but is run in a specified stage. One example of this would be an iptables module which offers a define that can be used by other modules to input rules for the firewall. It could go a little something like this. iptables.pp: class iptables { file{''/etc/firewall/baserules'':} service{"firewall": ensure => running, } } define open_internal_port(port) { file{"/etc/firewall/${name}": content = template("internal_rule.erb") } File["/etc/firewall/${name}"] ~> Service[''firewall''] } define open_external_port(port) { file{"/etc/firewall/${name}": content = template("external_rule.erb") } File["/etc/firewall/${name}"] ~> Service[''firewall''] } mysql.pp class mysql( $firewall_mode = "closed" ) { case $firewall_mode { ''closed'' => {} ''internal'' => { open_internal_port(3306) } ''external'' => { open_external_port(3306) } default => { fail("no valid firewall_mode specified: $ {firewall_mode}") } } }> > This sounds a lot like the Anchor pattern [2]. Are you trying to > accomplish the same goal of classes encapsulating other classes in the > dependency graph?After reading the wiki, it is indeed the anchor pattern.> Not easily. =) The anchor pattern is a bug, certainly, and so is the > UX of having to manage dependencies so carefully and painfully. The > good news is that we''re actively working on it. If you could update > any of the following bugs with your user stories and desires it will > help shape the solution. 11832, 12243, 12246, 12249, 12250, 12251, > 12253, 12255, 12256, 12257, 12258, 12259, 12260,I will have a look at adding my user stories to those tickets. Jos -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
> That''s one of many reasons to not do that. Specifically, one should > employ class inheritance only when it involves overriding resource > properties of the parent class.We have a history of using class inheritence to override variables in template''s or add extra functionality the base classes. Both practices can now be abandoned with the use of parameterized classes. Where all you really want is to compose classes (as I infer is the case you describe), you should> instead use the ''include'' function, the ''require'' function, or a resource-style class declaration.Exactly where we are going.> If you use class inheritance *only* to override resource properties > (subclass bodies contain nothing but resource overrides) then I think > your subclasses will not suffer from the problem you describe.Hmm, good point, but I really hope we can get away with just parameterized classes instead of overrides.> You will already have recognized that none of this solves your problem > directly. Effective dependency management requires discipline, > planning, documentation, and the occasional large-scale refactoring. > Correct use of class inheritance fits in mostly by shaping the > expectations and practices of manifest developers, rather than by > providing tools to directly control ordering. I hope you nevertheless > find this useful.This sure is usefull, talking helps to understand the problem and the pro''s/con''s of different solutions. Jos Houtman -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.
On 01/30/2012 05:30 AM, Jeff McCune wrote:> In the meantime, I document and leave it up to the end user to create > another class that composes modules together. With your example, I''d > do something like this as the end user. As the module author, I''d try > and write mysql and ldap as if the other didn''t exist. > > node www1 { > include site::dbserver > } > > #<modulepath>/site/manifests/dbserver.pp > # This is what it means to be a database at the Acme.com site. > class site::dbserver { > class { ldap: } > -> class { mysql: } > } > > [snip]Jeff, thanks for this. Its easy to get confused about how to do this correctly. This example and way of explaining how modules should be independent is very important and maybe not visible enough in the current documentation. Jos, I disagree with you about stages. I feel stages should be the VERY LAST resort .. before going to a design that require two Puppet runs ;-) I do think Puppetlabs should teach and weigh in more on site design in the community. I''ve sent some tweets to Gary Larizza about having some challenges in coming up with a sensible design and I''m happy to see what we''ve done is in line with Jeffs example. But it took us a good YEAR to get there. Maybe we''re just slow. What was an eye-opener on site design AND simplicity for us was Jasper Poppes talk about "Structuring Puppet" at Puppetconf. We learned a lot from looking at his modules. Jasper, if you read this, thank you! http://www.slideshare.net/PuppetLabs/puppetconf2011-small#21 -- http://www.uib.no/personer/Jan.Ivar.Beddari -- You received this message because you are subscribed to the Google Groups "Puppet Users" group. To post to this group, send email to puppet-users@googlegroups.com. To unsubscribe from this group, send email to puppet-users+unsubscribe@googlegroups.com. For more options, visit this group at http://groups.google.com/group/puppet-users?hl=en.