# # Copyright (c) 2015 NetApp, Inc., All Rights Reserved. # Any other use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @pod here ## @author dl-nacl-dev@netapp.com ## @summary The base class for all Xgen configuration modules package NACL::Service::Xgen::Config; use strict; use warnings; use Digest::MD5 qw(md5_hex); use NATE::Log qw(log_global); use NATE::Exceptions::Argument; use NACL::Service::Xgen::Exceptions::NoResourcesFound; sub new { my ($class, %args) = @_; my $seed = delete($args{random_seed}) // "fixed_seed"; my $self = \%args; bless($self, $class); # initialize the resource hash $self->{_resource_hash} = {}; # set a random seed for the config if ($seed) { log_global()->log("Random seed used for config: $seed"); $seed = srand(unpack ("N", md5_hex($seed))); $self->{_random_seed} = $seed; } return $self; } # stub for build method to be called by Xgen.pm sub build { my ($self, %args); # no-op } sub run_formatter { my ($self, %args) = @_; my $formatter_obj = delete($args{formatter}) // NATE::Exceptions::Argument->throw("'formatter' is a required parameter"); if (!$formatter_obj->isa("NACL::Service::Xgen::Formatter")) { NATE::Exceptions::Argument->throw("'formatter' is not a valid NACL::Service::Xgen::Formatter object"); } # run to format on every resource foreach my $resource_type (keys(%{$self->{_resource_hash}})) { @{$self->{_resource_hash}->{$resource_type}} = map { $_->to_format(formatter => $formatter_obj) } @{$self->{_resource_hash}->{$resource_type}}; } return $self->{_resource_hash}; } sub add_resources { my ($self, %args) = @_; my $value = delete($args{resources}) // NATE::Exceptions::Argument->throw("'resources' is a required parameter"); if (ref($value) ne 'ARRAY') { NATE::Exceptions::Argument->throw("'resources' must be an array reference"); } foreach my $resource (@{$value}) { my $type = ref($resource); push(@{$self->{_resource_hash}->{$type}}, $resource); } } sub get_resources { my ($self, %args) = @_; my $type = delete($args{type}) // NATE::Exceptions::Argument->throw("'type' is a required parameter"); if (not exists($self->{_resource_hash}->{$type})) { return []; } else { return $self->{_resource_hash}->{$type}; } } sub remove_resources { my ($self, %args) = @_; my $value = delete($args{resources}) // NATE::Exceptions::Argument->throw("'value' is a required parameter"); my $delete = delete($args{delete}) // 1; if (ref($value) ne 'ARRAY') { NATE::Exceptions::Argument->throw("'value' must be an array reference"); } # walk each resource to remove from the config and cleanup the resource hash foreach my $resource (@{$value}) { # first see if this resource type is in the hash if ($self->{_resource_hash}->{ref($resource)}) { # it is, extract the resource object from the hash for (my $i = 0; $i < scalar(@{$self->{_resource_hash}->{ref($resource)}}); $i++) { my $r = $self->{_resource_hash}->{ref($resource)}->[$i]; if ($r eq $resource) { # we need to splice this resource out of the resource hash splice(@{$self->{_resource_hash}->{ref($resource)}}, $i, 1); # delete the resource if we were asked to do it if ($delete) { # when we delete the resource, it returns back all the resources by # deleting $resource my @resources_deleted = $resource->delete_resource(); # pull off the first resource in the list because it is the resource # that we just deleted shift(@resources_deleted); # if there are any dependencies that were deleted, we need to remove # them from the config. disable the delete flag since technically these # resources have already been deleted. if (scalar(@resources_deleted)) { $self->remove_resources(resources => \@resources_deleted, delete => 0); } } last; } } # if there are no more left, clean up this resource type # from the hash if (! scalar(@{$self->{_resource_hash}->{ref($resource)}})) { delete($self->{_resource_hash}->{ref($resource)}); } } else { # resource doesnt exist in config NACL::Service::Xgen::Exceptions::NoResourcesFound->throw("Cannot remove the resource " . ref($resource) . " from the config because it does not exist."); } } } 1; =head1 NAME NACL::Service::Xgen::Config =head1 SYNOPSIS package My::Config; # make the config a derived class of the base class use parent qw(NACL::Service::Xgen::Config); sub build { my ($self, %args) = @_; # ... create and add resources } 1; =head1 DESCRIPTION C is an abstract class for configuration objects. It is the base class for all configurations. A configuration is a logical representation of a collection of resource objects (see L|lib-NACL-Service-Xgen-Resource-pm>) Configurations can be derived off of other configurations to create an elaborate configuration dependency tree. This class should not be instatiated directly. To use this class properly, it is best to create a new configuration class that is derived from this one (or from another configuration). To implement a configuration module, you will typically override the C method and have it create or manipulate resources. Usually configuration libraries are never directly manipulated with outside of the L|lib-NACL-ServiceAPI-Xgen-pm> library. Please refer to the documentation in that library for details on how to build a configuration module. =head1 CONFIGURATION CATALOG There is no official configuration catalog, configuration libraries can be placed anywhere in the test codeline. =head1 METHODS =head2 new This is the constructor for the configuration module, typically it need not be invoked directly. =over =item Synopsis use My::Config; # isa NACL::Service::Xgen::Config my $config = My::Config->new(); =item Arguments =over =item C<< random_seed => $scalar >> (Optional) The seed to install into the configuration to allow for repeatable randomness. Default: "fixed_seed" =item C<< %passthrough_args >> (Optional) All parameters passed into the constructor are stored as keys on the configuration object. =back =back =head2 build To implement a configuration module, you must implement the C routine. This method is intended to be responsible for generating resource objects and registering them with the configuration module via the C methods. =over =item Synopsis package My::Config; use parent qw(NACL::Service::Xgen::Config); sub build { my ($self, %args) = @_; # ... create resource objects and register them via '$self->add_resources()' } 1; =item Arguments =over =item C<< %args >> (Optional) Passthrough arguments to be used by the build routine. =back =back =head2 add_resources Register L|lib-NACL-Service-Xgen-Resource-pm> objects with the configuration. =over =item Synopsis package My::Config; use parent qw(NACL::Service::Xgen::Config); sub build { my ($self, %args) = @_; require My::Resource::Foo; require My::Resource::Bar; my $r1 = My::Resource::Foo->new(); my $r2 = My::Resource::Bar->new(); $self->add_resources(resources => [$r1,$r2]); } 1; =item Arguments =over =item C<< resources => \@arrayref >> (Required) An array reference of L|lib-NACL-Service-Xgen-Resource-pm> objects to add to the configuration =back =back =head2 get_resources Obtain L|lib-NACL-Service-Xgen-Resource-pm> objects that have already been registered with the configuration via a specific type. =over =item Synopsis package My::Config; use parent qw(NACL::Service::Xgen::Config); sub build { my ($self, %args) = @_; # add some resources ... my $foo_resources = $self->get_resources(type => "My::Resource::Bar"); foreach my $resource (@{$foo_resources}) { # ... } } 1; =item Arguments =over =item C<< type => $scalar >> (Required) The class name of the resources you wish to obtain. This must match a resource library's package name. =back =back =head2 remove_resources Remove L|lib-NACL-Service-Xgen-Resource-pm> objects from the configuration. =over =item Synopsis package My::Config; use parent qw(NACL::Service::Xgen::Config); sub build { my ($self, %args) = @_; # add some resources ... my $bar_resources = $self->get_resources(type => "My::Resource::Bar"); # remove all the bar resources from the config $self->remove_resources(resources => $bar_resources); } 1; =item Arguments =over =item C<< resources => \@arrayref >> (Required) An array reference of L|lib-NACL-Service-Xgen-Resource-pm> objects to remove from the configuration. =item C<< delete => $scalar >> (Optional) If enabled, will also delete the resource. If a resource is deleted, all of its descendents will be removed (and deleted) from the configuration. See L|lib-NACL-Service-Xgen-Resource-pm/delete_resource>> to understand what it means when a resource is deleted. Default: 1 =back =item Exceptions Thrown L|lib-NACL-Service-Xgen-Exceptions-NoResourcesFound-pm - if a resource does not exist on the configuration. =back =head2 run_formatter Feed the configuration into a L|lib-NACL-Service-Xgen-Formatter-pm> for processing. The end result will be a formatted configuration datastructure. =over =item Synopsis my $config = My::Config->new(); $config->build(); my $formatter = My::Formatter->new(); my $formatted_configuration = $config->run_formatter(formatter => $formatter); =item Arguments =over =item C<< formatter => \$NACL::Service::Xgen::Formatter >> (Required) A L|lib-NACL-Service-Xgen-Formatter-pm> object to handle the processing of the configuration. =back =item Return Value A hash datastructure of the formatted configuration after it has been processed by the formatter object specified. =back =head1 SEE ALSO See L|lib-NACL-Service-Xgen-Examples-ExampleConfig-pm> for an example implementation of a configuration module. =head1 AUTHOR/MAINTAINER =over =item NACL Development (dl-nacl-dev@netapp.com) =back =cut