# $Id # # Copyright (c) 2012-2017 NetApp, Inc., All Rights Reserved # Any use, modification, or distribution is prohibited # without prior written consent from NetApp, Inc. # ## @summary InstantComponent Module ## @author dl-nacl-dev@netapp.com ## @status public ## @pod here =head1 NAME NACL::InstantComponent =head1 DESCRIPTION C contains functions related to Instant Component Generation =head1 METHODS =head2 generate_pod generate_pod(command_interface => $ci, component_name => $component_name, method => $method_name); (or) generate_pod(build_root => $build_root, component_name => $component_name, method => $method_name); Example: use NACL::C::Node; use NACL::InstantComponent qw(generate_pod); my $ci = NACL::C::Node->find(); generate_pod(command_interface => $ci, component_name => 'NACL::C::StorageAggregate', method => 'show_scrub_status'); This function will print the POD for the component method passed in. Method name is optional , if its not passed it would generate the POD for all the methods in that component.Inorder to run this function pass build_root or command interface object of a filer which has the desired version of ONTAP. If command_interface and build_root is passed , build_root will be given preference =over =item Options =over =item C<< command_interface => $command_interface >> (Optional) See L Either command_interface or build_root should be passed =item C<< build_root => $build_root >> (Optional) Build root path , Example: /x/eng/rlse/DOT/mainN Pass command_interface or build_root It is recommended to use this option only for SN and above =item C<< component_name => $pkg_name >> (Required) Name of the component library =item C<< method => $method >> (Optional) Name of the method in component library =back =back =cut package NACL::InstantComponent; use strict; use warnings; use Carp qw/confess/; use List::Compare; use autouse 'NACL::Tools::CGT::CGTUtils' => qw(parse_leaf_level_cmd determine_pk processCmds _update_method_name _set_zapi_on_object _load_event_class); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); use Params::Validate qw(validate_with validate_pos :types ); use NACL::Tools::CGT::ComponentMethod; use NACL::Tools::CGT::ComponentStateFile; use NACL::ComponentUtils qw( _ontap_ci_validate_spec _convert_component_to_api _is_nacl_lib_loaded nate_autouse_available); use Data::Dumper qw(Dumper); use NACL::Tools::CGT::ComponentDS; use autouse 'NACL::Tools::CGT::CgtXml' => qw(processdirs parse_xmlfile _get_help_xml_path cli_to_zapi ); use NACL::APISet::Schema; use NACL::APISet::Exceptions::MethodNotFoundException qw(:try); use NATE::ParamSet qw( param_global); use autouse 'STB::Util' => qw(is_non_netapp_site); use autouse 'Smoke' => qw(runningInBubble); BEGIN { use Exporter qw(import); our @EXPORT_OK = qw( generate_pod _generate_subcommand_ds _generate_method instant_component ); } # # Function which generates the POD # sub generate_pod { my ($pod, $ds, $cdef, $exception, $command, $schema); my %opts = validate_with( params => \@_, spec => { component_name => {type => SCALAR}, method => {type => SCALAR, optional => 1}, build_root => {type => SCALAR, optional => 1}, build_arch_type => {type => SCALAR, optional => 1}, command_interface => { type => OBJECT, isa => 'NACL::C::CommandInterface::ONTAP', optional => 1 } } ); my $fh = \*ntest::NTEST_STDOUT; if (!$opts{command_interface} && !$opts{build_root}) { NATE::Exceptions::Argument->throw( "Pass build_root or command_interface"); } if ($opts{method}) { if ($opts{build_root}) { my $xml_file = _get_help_xml_path(build_root => $opts{build_root},); $opts{component_name} =~ /.*::(.*)$/; my $comp_name = $1; $comp_name = _convert_component_to_api($comp_name); my $method_name = $opts{method}; my $api_cmd = $comp_name . "_" . $method_name; $schema = _create_schema_obj(build_root => $opts{build_root}); $cdef = $schema->get_command_definition(command => $api_cmd); my $command = $cdef->{command}; $command =~ s/\s/\//g; $command .= ".xml"; $xml_file .= $command; $ds = parse_xmlfile(xml_file => $xml_file); } else { $ds = _generate_subcommand_ds( pkg_name => $opts{component_name}, method_name => $opts{method}, command_interface => $opts{command_interface} ); } $pod = _generate_method( comp_method_hashref => $ds, pkg_name => $opts{component_name}, method => $opts{method}, generate_pod_alone => 1 ); } else { $opts{component_name} =~ /.*::(.*)$/; my $comp_name = $1; my $start_cmd = _convert_component_to_api($comp_name); if ($opts{build_root}) { my $cmd = $start_cmd; $cmd =~ s/_/\//g; my $path = _get_help_xml_path(build_root => $opts{build_root},) . $cmd; if (!-e $path) { $start_cmd = _guess_start_cmd( build_root => $opts{build_root}, start_cmd => $start_cmd ); } else { $start_cmd =~ s/_/ /g; } $Log->debug("\n start_cmd: " . $start_cmd); $ds = processdirs( build_root => $opts{build_root}, start_cmd => $start_cmd, skip_subdirs => 1 ); } else { $start_cmd =~ s/_/ /g; $ds = _walk_command_ui( command_interface => $opts{command_interface}, start_cmd => $start_cmd ); } $pod = _generate_component_pod(component_ds => $ds); } print $fh $pod; $Log->exit() if $may_exit; } ## end sub generate_pod # # Private function which takes component hashref as an input , # generates POD/method and returns it as a string # If generate_pod_alone => 1 , it returns the POD # If generate_method_alone => 1, it returns the method # sub _generate_method { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { comp_method_hashref => {type => HASHREF}, pkg_name => {type => SCALAR}, error_ptr => {type => SCALARREF, optional => 1}, generate_pod_alone => {type => SCALAR, default => 0}, generate_method_alone => {type => SCALAR, default => 0}, method => {type => SCALAR}, } ); my (@primary_keys, @optional_primary_keys, $generated_code); my $generate_pod_alone = $opts{generate_pod_alone}; my $generate_method_alone = $opts{generate_method_alone}; my $comp_method_hashref = $opts{comp_method_hashref}; my $pkg_name = $opts{pkg_name}; my $error_ptr = $opts{error_ptr}; $pkg_name =~ /.*::(.*)$/; my $comp_name = $1; my $api_method = $comp_method_hashref->{command}; my $method_name = $opts{method}; @primary_keys = @{$comp_method_hashref->{primary_keys}} if ( $comp_method_hashref->{primary_keys} ); @optional_primary_keys = @{$comp_method_hashref->{optional_primary_keys}} if ( $comp_method_hashref->{optional_primary_keys} ); my $package_var; my $create_object_flag = 0; if (($method_name =~ /create/) or ( ($method_name eq 'start') and ($comp_method_hashref->{is_static})) or ($method_name eq 'copy') ) { $create_object_flag = 1; } if (($comp_method_hashref->{is_static}) or ($create_object_flag)) { $package_var = '$pkg'; } else { $package_var = '$pkg_or_obj'; } my $fields = $comp_method_hashref->{fields}; my (@options, @array_list, @optional_scalars); while (my ($field_name, $field_hashref) = each %$fields) { push @options, $field_name; if ($field_hashref->{is_array}) { push @array_list, $field_name; } else { if (!$comp_method_hashref->{can_use_cvw}) { push @optional_scalars, $field_name; } elsif ( !grep { $_ eq $field_name } @primary_keys ) { push @optional_scalars, $field_name; } } } my $component_method = NACL::Tools::CGT::ComponentMethod->new( method_name => $method_name, package_var => $package_var, create_object_flag => $create_object_flag, comp_method_hashref => $comp_method_hashref, api_method => $api_method, package_name => $opts{pkg_name}, filename => $comp_name, vserver_context => $comp_method_hashref->{vserver_context}, ); $component_method->options(@options); $component_method->array_list(@array_list); $component_method->optional_scalars(@optional_scalars); $component_method->primary_keys(@primary_keys); $component_method->optional_primary_keys(@optional_primary_keys); $component_method->generate_pod_alone($generate_pod_alone); $component_method->generate_method_alone($generate_method_alone); _set_zapi_on_object($component_method, $comp_method_hashref); # # If -iter ZAPI is chosen, CLI to ZAPI mapping would be obtained # from show.xml, $comp_method_hashref->{cs} contains the parsed # content of show.xml if ($component_method->iter_zapi_isset()) { $component_method->cs_method_hashref($comp_method_hashref->{cs}) if ($comp_method_hashref->{cs}); } if ($comp_method_hashref->{is_CS}) { my @temp = map { ucfirst($_) } split(/_/, $method_name); my $temp_pkg_name = $pkg_name; $pkg_name =~ s/.*::(.*$)/$1/g; my $filename = $pkg_name . join("", @temp); my $CS_package_name = 'NACL::CS::' . $filename; if (!$generate_pod_alone) { _generate_cs_package(CS_pkg_name => $filename, C_pkg_name => $pkg_name, ds => $comp_method_hashref, is_CS_like => 1,); } $generated_code = $component_method->build_cs_like_frontend_method($filename); } else { if ($generate_pod_alone) { $generated_code = $component_method->build_c_frontend_method(); } else { #Generate the backend first and add it to namespace $generated_code = $component_method->build_c_backend_method(); $generated_code =~ s/require.*?;//g; ## build check method $generated_code .= $component_method->build_c_check_method(); if (nate_autouse_available()){ if ( NATE::Autouse->is_autoused($pkg_name)){ # We are about to redefine the package in the eval below # Get rid of all the autouse hooks NATE::Autouse->unautouse($pkg_name); } } my $coderef = eval "package $pkg_name;use Params::Validate qw(SCALARREF);use NACL::CS::ComponentState::ZapiSkip qw(make_zapi_skip);use NACL::CS::ComponentState::ZapiArray qw(make_zapi_array);$generated_code"; if ($@) { $Log->debug("Could not eval the generated code: \n" . "$generated_code" . "Errors : \n" . $@); $error_ptr .= "Could not eval the generated method Errors: $@"; return; } #Generate frontend and return it $generated_code = $component_method->build_c_frontend_method(); } } $Log->exit() if $may_exit; return $generated_code; } ## end sub _generate_method # # Private function which generates the component hashref. # It tries to parse the xml files from build root ,if its # not sucessful it would walk the UI # # sub _generate_subcommand_ds { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { pkg_name => {type => SCALAR}, method_name => {type => SCALAR}, command_interface => _ontap_ci_validate_spec(), cdefs_from_local => {type => SCALAR , default => 0}, } ); my $api = _convert_component_to_api($opts{pkg_name}); my $method = $opts{method_name}; my $ci = $opts{command_interface}; #The following line is required to handle the #case where method name matches with primary #key attribute, in such cases the method #name is the api method name itself #Ex: The debug_smbdb_table method in NACL::C::DebugSmdb #component ,but the actually method name should have been #table $method =~ s/${api}_//g; my $api_cmd = $api . "_" . $method; my $command; my $exception; try { # CDEFS_FROM_LOCAL is a commandline parameter which is used to load cdefs from NACL(Open Lab) # If we are running in a smoke bubble, do not invoke the is_non_netapp_site() function. # It is extremely innefficient when invoked in the smoke bubbles if ( defined param_global->get('CDEFS_FROM_LOCAL') or (!runningInBubble() and is_non_netapp_site()) or $opts{cdefs_from_local} ) { $command = $ci->translate_cm_cli_api_to_command( api => $api_cmd ); } else { $command = $ci->translate_cm_cli_api_to_command( api => $api_cmd, apiset_new_options => { version_info => {cdefs_from_build => 1} } ); } } catch NACL::APISet::Exceptions::MethodNotFoundException with { $exception = 1; }; if ($exception) { $Log->debug("\n cdef not found for $api_cmd"); $Log->exit() if $may_exit; return; } my $help_xml_obj = NACL::Realm->realm()->get_help_xml_obj(command_interface => $ci); my $ds; if ($help_xml_obj->help_xml_accessible()) { $ds = $help_xml_obj->get_attribute_map(command => $command); #The following chunk of code is for instant ZAPIs #If a show.xml exists it would be parsed add to the command #data structure, this is required for getting the mapping informaion #for -iter Zapi. if ($help_xml_obj->cli_to_zapi_accessible()) { my $zapi = $help_xml_obj->get_cli_to_zapi_mapping(command => $command); if ($zapi) { $ds->{'zapi-command'} = $zapi; if (grep(/-iter$/, @{$zapi}) && $ds->{command} !~ /_show$/) { $command =~ /(.*)\s.*$/; my $top_level_cmd = $1; my $show_cmd = "$top_level_cmd show"; my $show_ds = $help_xml_obj->get_attribute_map(command => $show_cmd); $ds->{cs} = $show_ds if ($show_ds); } } } } if (!$ds) { $ds = $help_xml_obj->get_ui_details(command_interface => $ci, api => $api_cmd); $ds->{command} = $api_cmd; $Log->debug(sub {"\n Dump from UI: " . Dumper($ds)}); } if (!$ds) { $Log->exit() if $may_exit; return $ds; } _determine_other_details( ds => $ds, comp_name => $opts{pkg_name}, command_interface => $opts{command_interface} ); $Log->exit() if $may_exit; return $ds; } ## end sub _generate_subcommand_ds # # Private function which determines extra information , like whether method call is # static or not and can it use common_validate_with or not # sub _determine_other_details { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { ds => {type => HASHREF}, comp_name => {type => SCALAR}, command_interface => _ontap_ci_validate_spec(), } ); my $component_method_ref = $opts{ds}; my $command = $component_method_ref->{command}; my $compname = $opts{comp_name}; my $api = $compname; $api =~ s/\s|-/_/g; my $show_api = $api . "_show"; if ($command eq $show_api) { $component_method_ref->{is_static} = 1; $component_method_ref->{can_use_cvw} = 0; } else { my $can_use_cvw = 1; my $is_static = 0; my $ci = $opts{command_interface}; my @non_optional_pks; my %pk_spec; eval { %pk_spec = $compname->_primary_keys_validate_spec( command_interface => $ci); }; my $do_require = 0; if ( $@ && $@ =~ /Can't locate object method.*perhaps you forgot to load/) { $do_require = 1; } if ($do_require) { eval "require $compname"; if ($@) { $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "Could not import package $compname Error: $@"); } %pk_spec = $compname->_primary_keys_validate_spec( command_interface => $ci); } map { if (!defined $pk_spec{$_}->{optional}) { push @non_optional_pks, $_; } } keys %pk_spec; my $actual_pk_list = $component_method_ref->{primary_keys} if (exists $component_method_ref->{primary_keys}); $component_method_ref->{primary_keys} = \@non_optional_pks; my %fields_hash; my @fields = keys %{$component_method_ref->{fields}}; map { $fields_hash{$_} = 1 } @fields; foreach my $primary_key (@non_optional_pks) { if (!exists $fields_hash{$primary_key}) { $can_use_cvw = 0; last; } } my $lc = List::Compare->new('--unsorted', $actual_pk_list, $component_method_ref->{primary_keys}); if (!$lc->is_LequivalentR()) { # as per burt#1102715, need to set ignore_primary_keys => 1 # for _common_validate_with() if there is a mismatch # b/w mandatory keys for that command and primary keys. $can_use_cvw = 0; } $component_method_ref->{can_use_cvw} = $can_use_cvw; # can_use_cvw = 0 implies it's static, but $can_use_cvw = 1 does not # mean it's an instance variable. For 'create' or 'start' methods # $can_use_cvw will be 1, but are static methods. Hence we need # to store both if ($command =~ /create/ || $can_use_cvw == 0) { $is_static = 1; } $component_method_ref->{is_static} = $is_static; } $Log->exit() if $may_exit; } ## end sub _determine_other_details # # Private function which adds the CS package to the namespace # sub _generate_cs_package { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { C_pkg_name => {type => SCALAR}, CS_pkg_name => {type => SCALAR}, ds => {type => HASHREF}, is_CS_like => {type =>SCALAR, default => 0} } ); my $c_pkg_name = $opts{C_pkg_name}; my $cs_pkg_name = $opts{CS_pkg_name}; my $ds = $opts{ds}; my $show_cmd = $ds->{command}; my $Comp_package_name = "NACL::C::$c_pkg_name;"; my $CompState_package_name = "NACL::CS::$cs_pkg_name;"; my $cs_file = NACL::Tools::CGT::ComponentStateFile->new( CS_package_name => $CompState_package_name, C_package_name => $Comp_package_name, component_name => $c_pkg_name, ds => $ds, command => $show_cmd, generate_method_alone => 1, ); _set_zapi_on_object($cs_file, $ds); my @array = keys %{$ds->{fields}}; $cs_file->fields(@array); if ( $opts{is_CS_like} ) { $cs_file->is_CS_like(1); } if ($ds->{is_singleton}) { $cs_file->is_singleton($ds->{is_singleton}); } my $cs_pkg = $cs_file->build_attributes_includes(); $cs_pkg .= $cs_file->build_cs_new(); $cs_pkg .= $cs_file->build_cs_check_method() ; $cs_pkg .= $cs_file->generate_extra_filter_fields() if ( $ds->{extra_filter_fields} ); $cs_pkg .= $cs_file->build_cs_fetch_frontend(); my $value_convert_method = $cs_file->build_update_state_obj_method(); if ( $value_convert_method ) { $cs_pkg .= $value_convert_method; } $cs_pkg .= $cs_file->build_cs_fetch_backend(); $Log->comment( sub {"Generated CS package : " .Dumper($cs_pkg)}) if (defined param_global->get('NACL_BURT_987443')) ; if (nate_autouse_available()){ if (NATE::Autouse->is_autoused("NACL::CS::$cs_pkg_name")){ # We are about to create the package in memory in the eval below # Remove all autouse hooks NATE::Autouse->unautouse("NACL::CS::$cs_pkg_name"); } } eval "$cs_pkg"; if ($@) { NATE::BaseException->throw("Could not eval the generated code: \n" . "$cs_pkg" . "Errors : \n" . $@); } $Log->exit() if $may_exit; } ## end sub _generate_cs_package # # Private function which adds the C package to the namespace # sub _generate_c_package { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { pkg_name => {type => SCALAR}, keys => {type => ARRAYREF | UNDEF}, optional_keys => {type => ARRAYREF | UNDEF}, } ); my $pkg_name = $opts{pkg_name}; my $keys_ref = $opts{keys}; my $optional_keys_ref = $opts{optional_keys}; my $c_pkg = "package NACL::C::$pkg_name;"; $c_pkg .= <<'HEREDOC'; use strict; use warnings; HEREDOC $c_pkg .= <<'HEREDOC'; use NACL::ComponentUtils qw (_optional_scalars Dumper); use base 'NACL::C::Component::ONTAP'; use Params::Validate qw(SCALAR ARRAYREF); use NATE::Log qw(log_global); my $Log = log_global(); my $may_enter = $Log->may_enter(); my $may_exit = $Log->may_exit(); HEREDOC $c_pkg .= _load_event_class(); my (@keys, @optional_keys); @keys = @$keys_ref if $keys_ref; @optional_keys = @$optional_keys_ref if $optional_keys_ref; my @allkeys = (@keys, @optional_keys); if ( @allkeys ) { $c_pkg .= <<'HEREDOC'; use Class::MethodMaker [ HEREDOC foreach my $key (@allkeys) { $key =~ s/-/_/g; $c_pkg .= "scalar => '$key',"; } $c_pkg .= <<'HEREDOC'; ]; sub _primary_keys_validate_spec { return ( HEREDOC foreach my $key (@keys) { $c_pkg .= "'$key' => { type => SCALAR },"; } foreach my $key (@optional_keys) { $c_pkg .= "'$key' => { type => SCALAR, optional => 1 },"; } $c_pkg .= <<'HEREDOC'; ); } HEREDOC } $c_pkg .= <<'HEREDOC'; 1; HEREDOC $Log->debug("Generated C pkg \n" . $c_pkg); my $full_pkg_name = "NACL::C::$pkg_name"; if ( NATE::Autouse->is_autoused($full_pkg_name)){ NATE::Autouse->unautouse($full_pkg_name); } eval "$c_pkg"; if ($@) { NATE::BaseException->throw("Could not eval the generated code: \n" . "$c_pkg" . "Errors : \n" . $@); } $Log->exit() if $may_exit; } ## end sub _generate_c_package =head2 instant_component This function provides support for component libraries which are not available or autogenerated yet. Inorder to use this function, it would require to have a filer/vsim with the desired build installed and cdefs available for the commands invoked by the component methods This function parses the help_xml files from build_root, if its unable to parse, it walks the the user interface, defines the component packages and adds it to the namespace The component package added would support cmode cli and zapi. Example: Lets assume that Volume component is not available, code snippet for creating and deleting a volume will be as follows: use NACL::InstantComponent qw(instant_component); use NACL::C::Node; my $ci = NACL::C::Node->find(); instant_component(component_name => 'NACL::C::Volume', command_interface => $ci); my $vol_obj = NACL::C::Volume->create(command_interface => $ci, aggregate => 'component_flex_aggr', volume => 'test_vol', 'security-style' => 'ntfs', vserver => 'vs0'); my $size = $vol_obj->get_one_state_attribute('size'); $Log->comment("\n size:".$size); $vol_obj->offline(); $vol_obj->delete(); =over =item Options =over =item C<< command_interface=>$command_interface >> (Required for class method, Not Applicable for instance method) See L =item C<< component_name=>$comp_name >> (optional) Name of the component Either component_name or component_names should be passed, not both =item C<< component_names=>[ $comp_name ]>> (optional) Arrayref containing the list of component names to be installed Either component_name or component_names should be passed, not both =back =back =cut sub instant_component { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { component_name => {type => SCALAR, optional => 1}, component_names => {type => ARRAYREF, optional => 1 }, command_interface => _ontap_ci_validate_spec(), } ); my ($ds, $keys, $optional_keys, @names, $pk_ds); if ( $opts{component_name} && $opts{component_names} ) { NATE::Exceptions::Argument->throw("Either component_name or component_names" ."should be passed to the call, not both"); } if ( !$opts{component_name} && !$opts{component_names} ) { NATE::Exceptions::Argument->throw("Either component_name or component_names" ."should be passed to the call"); } my $ci = $opts{command_interface}; if (!$ci->is_cmode()) { NATE::Exceptions::Argument->throw( 'Instant component support is available only for CMode' ); } if ( $opts{component_name} ) { @names = ( $opts{component_name} ); } elsif ( $opts{component_names} ) { @names = @{ $opts{component_names} }; } my $help_xml_obj = NACL::Realm->realm()->get_help_xml_obj( command_interface => $ci); foreach my $pkg_name ( @names ) { if ( $pkg_name =~ /::CS::/ ) { $Log->warn("The instant_component function is meant for Component(C)". "lib not Component State(CS) lib \.Please update your script"); $pkg_name =~ s/::CS::/::C::/g; } $pkg_name =~ /.*::(.*)$/g; # $pkg_name will be NACL::C::Foo, $partial_pkg_name will be Foo my $partial_pkg_name = $1; #Check if the package is already loaded if (!_is_nacl_lib_loaded($pkg_name)) { #Try to do a require on the component lib, if its unable to #locate ,we can assume it s new component # If the package $pkg_name is autoused, the require below # won't do anything. If we are creating an instant component # there is a very good chance we'll be truly using the package soon # So un-autouse # Note that this only matters when creating instant components for # classes that also exists on disk. Some tests out there do that. if (nate_autouse_available()){ if ( NATE::Autouse->is_autoused($pkg_name)){ NATE::Autouse->unautouse($pkg_name); } } eval "require $pkg_name"; if ($@) { if ($@ =~ /Can't locate.*in \@INC/) { $Log->comment("\n Installing $pkg_name package..."); my $api = _convert_component_to_api($pkg_name); my $show_api = $api . "_show"; my $command; try { $command = $ci->translate_cm_cli_api_to_command(api => $show_api); } catch NACL::APISet::Exceptions::MethodNotFoundException with { # No show command for this Component }; if ($command) { #Check if the xml path is accessible if ($help_xml_obj->help_xml_accessible()) { my $cdef = $ci->apiset()->get_command_definition(command => $show_api); my $alias_map = $cdef->get_alias_map(); $ds = $help_xml_obj->get_attribute_map(command => $command, alias_map => $alias_map); } else { $ds = $help_xml_obj->get_ui_details( command_interface => $ci, api => $show_api ); } if ( $ds ) { $keys = $ds->{primary_keys}; $optional_keys = $ds->{optional_primary_keys}; if ($help_xml_obj->cli_to_zapi_accessible()) { my $zapi_cmd = $help_xml_obj->get_cli_to_zapi_mapping( command => $command); $ds->{'zapi-command'} = $zapi_cmd if ($zapi_cmd); } _generate_cs_package( C_pkg_name => $partial_pkg_name, CS_pkg_name => $partial_pkg_name, ds => $ds ); } } $Log->debug(sub {"Keys: " . Dumper($keys)}); if ( !$command ) { my ($dir, $cmd); my $found = 0; my $apiset = $ci->apiset(); my $all_cdefs = $apiset->get_schema_obj()->get_all_cdef_objects(); my @cdefs_keys = keys %{ $all_cdefs }; foreach my $cdef_key ( @cdefs_keys ) { $cmd = $all_cdefs->{$cdef_key}->{command}; $cmd =~ /(.*)\s+\S+/ if ( $cmd ); $dir = $1; if ( $dir ) { $dir =~ s/\s/_/g; $dir =~ s/-/_/g; if ( $dir eq $api ) { $found=1; last; } } } if ( !$found ) { $Log->exit() if $may_exit; NATE::BaseException->throw("\n Invalid component name "."$partial_pkg_name". " This could be because the command is invalid for the build". " (or) cdefs is not updated "); } } my $exception_pkg_name = "NACL::C::Exceptions::" . $partial_pkg_name . "::DoesNotExist"; _generate_exception_class($exception_pkg_name); $exception_pkg_name = "NACL::C::Exceptions::" . $partial_pkg_name . "::AlreadyExists"; _generate_exception_class($exception_pkg_name); _generate_c_package( pkg_name => $partial_pkg_name, keys => $keys, optional_keys => $optional_keys, ); } else { NATE::BaseException->throw("$@"); } } } } $Log->exit() if $may_exit; } ## end sub instant_component # # Private function which walks the UI for a command directory # and generates the component hashref # # sub _walk_command_ui { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { start_cmd => {type => SCALAR}, command_interface => _ontap_ci_validate_spec(), } ); my (%cmdsRef, $component_contains_create); my $cmdsRef = \%cmdsRef; my $apiset = $opts{command_interface}->apiset( category => 'Node', set => 'CMode', interface => 'CLI' ); my $start_cmd = $opts{start_cmd}; my $underscored_start_cmd = $start_cmd; $underscored_start_cmd =~ s/\s/_/g; $start_cmd = _guess_start_cmd( start_cmd => $underscored_start_cmd, schema => $apiset->get_schema_obj(), ); processCmds( cmd => $start_cmd, cmdHashRef => \%cmdsRef, walk_sub_command => 0, apiset => $apiset, ); my $api = $start_cmd; $api =~ s/\s|-/_/g; my $start_api = $api . "_start"; my $show_api = $api . "_show"; my $pk_hash_ref = determine_pk( apiset => $apiset, show_cmd => $show_api ); my @pks = ( @{ $pk_hash_ref->{primary_keys} }, @{ $pk_hash_ref->{optional_primary_keys} } ); while (my ($method, $method_hashref) = each %cmdsRef) { if ($method eq $show_api) { $method_hashref->{is_static} = 1; $method_hashref->{can_use_cvw} = 0; $method_hashref->{primary_keys} = $pk_hash_ref->{primary_keys}; $method_hashref->{optional_primary_keys} = $pk_hash_ref->{optional_primary_keys}; } else { my $can_use_cvw = 1; my $is_static = 0; my %fields_hash; if ($method =~ /create/) { $component_contains_create = 1; } my @fields = keys %{$method_hashref->{fields}}; map { $fields_hash{$_} = 1 } @fields; foreach my $primary_key (@pks) { if (!exists $fields_hash{$primary_key}) { $can_use_cvw = 0; last; } } $method_hashref->{can_use_cvw} = $can_use_cvw; # can_use_cvw = 0 implies it's static, but $can_use_cvw = 1 does not # mean it's an instance variable. For 'create' or 'start' methods # $can_use_cvw will be 1, but are static methods. Hence we need # to store both if ( ($method =~ /create/) or (!$can_use_cvw) or ( ($method eq $start_api) and (!$component_contains_create)) ) { $is_static = 1; } $method_hashref->{is_static} = $is_static; } $method_hashref->{command} = $method; _update_method_name( component_name => $start_cmd, method_ds => $method_hashref, primary_keys => \@pks ); } my %ds; my @methods = values %cmdsRef; $ds{$opts{start_cmd}} = \@methods; $Log->exit() if $may_exit; return \%ds; } ## end sub _walk_command_ui # # Private function which generates the POD for all the methods # in a component # sub _generate_component_pod { my %opts = validate_with( params => \@_, spec => {component_ds => {type => HASHREF},} ); $Log->enter() if $may_enter; my $component_ds = $opts{component_ds}; my ($current_comp_name, $current_comp_arr_ref) = each %$component_ds; my $show_api = $current_comp_name; $show_api =~ s/\s|-/_/g; $show_api .= "_show"; my $component = NACL::Tools::CGT::ComponentDS->new( component_name => $current_comp_name, generate_pod_alone => 1 ); $component->component_ds(@$current_comp_arr_ref); $component->determine_package_name(); $component->determine_CS_package_name(); my $str = $component->build_c_header(); $str .= "\n"; my @show_array = grep { $_->{command} eq $show_api } @$current_comp_arr_ref; my $primary_keys = $show_array[0]->{primary_keys}; $component->primary_keys(@$primary_keys); $str .= $component->build_c_attribute_list(); $str .= "\n"; $str .= $component->build_c_methods(); $Log->exit() if $may_exit; return $str; } ## end sub _generate_component_pod # #Private function which creates schema object # sub _create_schema_obj { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => {build_root => {type => SCALAR},} ); my $cdef_path = $opts{build_root} . "/final/bedrock/export/common/cdefsprod"; my $schema = NACL::APISet::Schema->new(); my $files = $schema->read_cdef_dir(directory => $cdef_path); $schema->load( files => $files, path => $opts{build_root} ); $Log->exit() if $may_exit; return $schema; } ## end sub _create_schema_obj # # This function tries to guess the actual command ,for a passed in # underscored command , Ex: if start_cmd => 'vserver_cifs_group_policy # it returns the actual command vserver cifs group-policy # sub _guess_start_cmd { $Log->enter() if $may_enter; my %opts = validate_with( params => \@_, spec => { build_root => {type => SCALAR, optional => 1}, start_cmd => {type => SCALAR}, schema => { type => OBJECT, optional => 1, isa => 'NACL::APISet::Schema', }, has_show => {type => SCALARREF, optional => 1}, } ); my ($exception, $cdef, $command); my $schema = $opts{schema}; if (!$schema) { $schema = _create_schema_obj(build_root => $opts{build_root}); } my $start_cmd = $opts{start_cmd}; my $orig_start_cmd = $start_cmd; $start_cmd .= "_show"; try { $cdef = $schema->get_command_definition(command => $start_cmd); my $show = $opts{has_show}; ${$show} = 1 if ($show); } catch NACL::APISet::Exceptions::MethodNotFoundException with { $exception = 1; }; if (!$exception) { $command = $cdef->{command}; $command =~ /(.*)\sshow$/g; $start_cmd = $1; } else { my $hash = $schema->get_all_cdef_objects(); my @api = keys %{$hash}; my @found = grep (/^$orig_start_cmd/, @api); if (@found) { $command = $hash->{$found[0]}->get_command(); $command =~ /(^.*)\s/; $start_cmd = $1; } else { $orig_start_cmd =~ s/_/ /g; $Log->exit() if $may_exit; NATE::Exceptions::Argument->throw( "cdef not found for $orig_start_cmd command"); } } $Log->exit() if $may_exit; return $start_cmd; } ## end sub _guess_start_cmd # #Private function which tries to load the passed in exception class #if not define it # sub _generate_exception_class { my ($exception_pkg_name) = (@_); eval "require $exception_pkg_name"; if ($@ =~ /Can't locate.*in \@INC/) { $exception_pkg_name =~ /.*::(.*)$/; my $exception_name=$1; my $str = "package $exception_pkg_name; # We need to make sure the parent is going to load even if its autoused # Importing :try effectively does that use NACL::C::Exceptions::$exception_name qw(:try); use base qw( NACL::C::Exceptions::$exception_name ); 1;"; eval "$str"; confess $@ if ($@); } } ## end sub _generate_exception_class 1;