use strict; use warnings; { package My::Serializer::Custom; use Moo; extends 'Role::REST::Client::Serializer'; sub _set_serializer { my $s = Data::Serializer::Raw->new( serializer => 'XML::Simple', options => { RootName => 'object' } ); return $s; } has '+serializer' => ( default => \&_set_serializer, ); 1; } package NGCP::Panel::Utils::DialogicImg; use Moo; use Types::Standard qw(Int HashRef); use HTTP::Tiny; with 'Role::REST::Client'; has '+type' => ( default => 'application/xml', is => 'rw' ); # has '+serializer_options' => (default => sub { # my $s = Data::Serializer::Raw->new( # serializer => 'XML::Simple', # options => { RootName => 'object' } ); # return { serializer => $s }; # }); has '+serializer_class' => ( is => 'rw', default => sub {'My::Serializer::Custom'} ); has '+clientattrs' => ( default => sub { return {timeout => 5}; }); has 'appid' => ( is => 'rw', isa => Int, default => 0 ); has 'pids' => ( is => 'rw', isa => HashRef, default => sub { return { root => 10_000, bn2020 => 10_001, }; } ); has 'classinfo' => ( is => 'ro', isa => HashRef, default => sub{ return { bn2020 => { # dummy, not used by _create_generic name => 'Node', parent => 'root', revalidate => 0, }, network => { name => 'NetworkInterfaces', parent => 'bn2020', revalidate => 0, }, interface_collection => { name => 'NetworkLogicalInterfaces', parent => 'network', revalidate => 0, }, interface => { name => 'NetworkLogicalInterface', parent => 'interface_collection', revalidate => 0, }, ip_address => { name => 'NetworkInterface', parent => 'interface', revalidate => 1, }, facility => { name => 'Facility', parent => 'bn2020', revalidate => 0, }, packet_facility_collection => { name => 'PacketFacilities', parent => 'facility', revalidate => 0, }, packet_facility => { name => 'PacketFacility', parent => 'packet_facility_collection', revalidate => 0, }, signaling => { name => 'Signaling', parent => 'bn2020', revalidate => 0, }, sip => { name => 'SIP', parent => 'signaling', revalidate => 0, }, sip_ip => { name => 'SIPIP', parent => 'sip', revalidate => 0, }, profile_collection => { name => 'Profiles', parent => 'root', revalidate => 0, }, ip_profile_collection => { name => 'IPProfiles', parent => 'profile_collection', revalidate => 0, }, ip_profile => { name => 'IPProfile', parent => 'ip_profile_collection', revalidate => 1, }, # ... vocoder_profile => { name => 'VocoderProfile', parent => 'ip_profile', revalidate => 1, }, sip_profile_collection => { name => 'SIPProfiles', parent => 'profile_collection', revalidate => 0, }, sip_profile => { name => 'SIPSGP', parent => 'sip_profile_collection', revalidate => 0, }, external_network_elements => { name => 'ExternalNetworkElements', parent => 'root', revalidate => 0, }, external_gateway_collection => { name => 'ExternalGateways', parent => 'external_network_elements', revalidate => 0, }, external_gateway => { name => 'ExternalGateway', parent => 'external_gateway_collection', revalidate => 1, }, routing_configuration => { name => 'RoutingConfiguration', parent => 'root', revalidate => 0, }, channel_group_collection => { name => 'ChannelGroups', parent => 'routing_configuration', revalidate => 0, }, channel_group => { name => 'ChannelGroup', parent => 'channel_group_collection', revalidate => 0, }, route_table_collection => { name => 'RoutingTables', parent => 'routing_configuration', revalidate => 0, }, route_table => { name => 'RouteTable', parent => 'route_table_collection', revalidate => 1, }, route_element => { name => 'RouteElement', parent => 'route_table', revalidate => 1, }, cg_network_element => { name => 'NetworkElement', parent => 'channel_group', revalidate => 0, }, node_association => { name => 'NodeAssociation', parent => 'cg_network_element', revalidate => 0, }, }; }); # returns appid or 0 sub login { my ( $self, $username, $password ) = @_; my $resp = $self->post( '/oamp/user_management/users/logged_in?appid=0', { user => { name => $username, password => $password } } ); my $code = $resp->code; my $data = $resp->data; if ( $code == 200 ) { $self->appid( $data->{appid} ); return $self->appid; } return 0; } sub obtain_lock { my ($self) = @_; $self->set_header( 'Content-Length' => '0', ); my $resp = $self->delete( '/oamp/user_management/locks?appid=' . $self->appid ); $resp = $self->post( '/oamp/user_management/locks?appid=' . $self->appid, {} ); return $resp; } ###### CREATE methods ###### sub create_bn2020 { my ($self) = @_; my $data = $self->objects->{bn2020}; my $resp = $self->post( '/oamp/configuration/objects/Node/NULL?pid=10000&appid=' . $self->appid, $data, ); if ( $resp->code == 200 ) { $self->pids->{bn2020} = $resp->data->{oid}; } return $resp; } sub create_network { my ($self) = @_; return $self->_create_generic(undef, 'network'); } sub create_interface_collection { my ($self) = @_; return $self->_create_generic(undef, 'interface_collection'); } sub create_interface { my ($self) = @_; my $resp = $self->_create_generic(undef, 'interface'); if ( $resp->code == 200 ) { if ( $resp->data->{property}{Interface}{value} eq "Control" ) { $self->pids->{interface_control} = $resp->data->{oid}; } elsif ( $resp->data->{property}{Interface}{value} eq "Data A" ) { $self->pids->{interface_dataa} = $resp->data->{oid}; } } return $resp; } sub create_ip_address { my ( $self, $options ) = @_; if ( defined $options->{NIIPAddress} && !defined $options->{NIIPGateway} ) { $options->{NIIPGateway} = $options->{NIIPAddress} =~ s/\.[0-9]+$/.1/r; } return $self->_create_generic($options, 'ip_address'); } sub create_facility { my ($self) = @_; return $self->_create_generic(undef, 'facility'); } sub create_packet_facility_collection { my ($self) = @_; return $self->_create_generic(undef, 'packet_facility_collection'); } sub create_packet_facility { my ( $self, $options ) = @_; return $self->_create_generic($options, 'packet_facility'); } sub create_signaling { my ( $self, $options ) = @_; return $self->_create_generic($options, 'signaling'); } sub create_sip { my ( $self, $options ) = @_; return $self->_create_generic($options, 'sip'); } sub create_sip_ip { my ( $self, $options ) = @_; return $self->_create_generic($options, 'sip_ip'); } sub create_profile_collection { my ( $self, $options ) = @_; return $self->_create_generic($options, 'profile_collection'); } sub create_ip_profile_collection { my ( $self, $options ) = @_; return $self->_create_generic($options, 'ip_profile_collection'); } sub create_ip_profile { my ( $self, $options ) = @_; return $self->_create_generic($options, 'ip_profile'); } sub create_vocoder_profile { my ( $self, $options ) = @_; return $self->_create_generic($options, 'vocoder_profile'); } sub create_sip_profile_collection { my ( $self, $options ) = @_; return $self->_create_generic($options, 'sip_profile_collection'); } sub create_sip_profile { my ( $self, $options ) = @_; return $self->_create_generic($options, 'sip_profile'); } sub create_external_network_elements { my ( $self ) = @_; return $self->_create_generic(undef, 'external_network_elements'); } sub create_external_gateway_collection { my ( $self, $options ) = @_; return $self->_create_generic($options, 'external_gateway_collection'); } sub create_external_gateway { my ( $self, $options ) = @_; return $self->_create_generic($options, 'external_gateway'); } sub create_routing_configuration { my ($self) = @_; return $self->_create_generic(undef, 'routing_configuration'); } sub create_channel_group_collection { my ($self) = @_; return $self->_create_generic(undef, 'channel_group_collection'); } sub create_channel_group { my ($self, $options) = @_; return $self->_create_generic($options, 'channel_group'); } sub create_route_table_collection { my ($self, $options) = @_; return $self->_create_generic($options, 'route_table_collection'); } sub create_route_table { my ($self, $options) = @_; return $self->_create_generic($options, 'route_table'); } sub create_route_element { my ($self, $options) = @_; return $self->_create_generic($options, 'route_element'); } sub create_cg_network_element { my ($self, $options) = @_; return $self->_create_generic($options, 'cg_network_element'); } sub create_node_association { my ($self, $options) = @_; return $self->_create_generic($options, 'node_association'); } sub _create_generic { my ($self, $options, $class) = @_; my $classinfo = $self->classinfo->{$class}; my $pid = $self->pids->{$classinfo->{parent}}; unless ($pid) { warn "$class: no valid pid available\n"; return; # TODO wrong format } my $classname = $classinfo->{name}; my $appid = $self->appid; my $resp = $self->get( "/oamp/configuration/objects/$classname/NULL?detaillevel=4&pid=$pid&appid=$appid", ); if ( $resp->code != 200 ) { warn "$class: Failed to fetch resource\n"; return $resp; } if ($classinfo->{revalidate}) { my $validation_data = $self->_build_validation_data( $resp->data, $pid, $options ); $resp = $self->get( "/oamp/configuration/objects/$classname/NULL", $validation_data, ); if ( $resp->code != 200 ) { warn "$class: Failed to fetch resource second time (revalidate)\n"; return $resp; } } my $new_data = $self->_build_response_data( $resp->data, $pid, $options ); $resp = $self->post( "/oamp/configuration/objects/$classname/NULL?pid=$pid&appid=$appid", $new_data, ); if ( $resp->code == 200 ) { $self->pids->{$class} = $resp->data->{oid}; } return $resp; } # log: 0: none, 1: short, 2: everything sub create_all_sipsip { my ($self, $settings, $log) = @_; $self->_create_indent; my $resp = $self->create_bn2020; my $schedule = [ {name => 'network', options => undef}, {name => 'interface_collection', options => undef}, {name => 'interface', options => undef}, {name => 'ip_address', options => { NIIPAddress => $settings->{ip1}, NIIPPhy => 'Services', }}, {name => 'interface', options => undef}, {name => 'ip_address', options => { NIIPAddress => $settings->{ip2}, NIIPPhy => 'Media 0', }}, {name => 'facility', options => undef}, {name => 'packet_facility_collection', options => undef}, {name => 'packet_facility', options => { ChannelCount => 50, }}, {name => 'signaling', options => undef}, {name => 'sip', options => undef}, {name => 'sip_ip', options => { IPAddress => $settings->{ip1}, }}, {name => 'profile_collection', options => undef}, {name => 'ip_profile_collection', options => undef}, {name => 'ip_profile', options => { DigitRelay => 'DTMF Packetized', Name => 'ngcp_in_profile', }}, {name => 'vocoder_profile', options => { PayloadType => 'G711 ulaw', }}, {name => 'vocoder_profile', options => { PayloadType => 'G711 alaw', }}, {name => 'sip_profile_collection', options => undef}, {name => 'sip_profile', options => undef}, #{run => 'download_profiles'}, {name => 'external_network_elements', options => undef}, {name => 'external_gateway_collection', options => undef}, {name => 'external_gateway', options => { Name => 'Phone1', IPAddress => $settings->{ip_client}, IPAddress4 => $settings->{ip_client}, }}, {name => 'routing_configuration', options => undef}, {name => 'channel_group_collection', options => undef}, {name => 'route_table_collection', options => undef}, {name => 'route_table', options => { Name => 'ngcp_route_table', }}, {name => 'channel_group', options => { SignalingType => 'SIP', InRouteTable => 'ngcp_route_table - ID: 5', InIPProfile => 'ngcp_in_profile', InIPProfileId => '1', OutIPProfile => 'ngcp_in_profile', # separate one for out? SupportA2F => 'True', }}, {name => 'cg_network_element', options => undef}, {name => 'node_association', options => undef}, {name => 'route_element', options => { StringType => 'Channel Group', InChannelGroup => 'ChannelGroup0', RouteActionType => 'Channel Group', RouteActionList => 'ChannelGroup0', }}, #{run => 'download_route_table'}, #{run => 'download_channel_groups'}, ]; for my $elem (@{ $schedule }) { my ($name, $options) = @{ $elem }{('name', 'options')}; my $fun = "create_$name"; $resp = $self->$fun($options); # $resp = $self->_create_generic($options, $name); if ($log >= 1) { my $ind = " " x ($self->classinfo->{$name}{indent}*4); printf "%-37s: %d\n", "$ind$name", $resp->code; if ($resp->code != 200) { use DDP; p $resp->data; } } } $self->download_profiles; $self->download_route_table; $self->download_channel_groups; return 0; } ###### OTHER STUFF ###### sub _create_indent { my ($self, @class) = @_; my $classinfo = $self->classinfo; @class = keys %{ $classinfo } unless @class; for my $class (@class) { next if ($classinfo->{$class}{indent}); my $parent = $classinfo->{$class}{parent}; if ($parent eq 'root') { $classinfo->{$class}{indent} = 0; } else { $self->_create_indent($parent); $classinfo->{$class}{indent} = $classinfo->{$parent}{indent} + 1; } } return; } sub download_profiles { my ($self) = @_; my $appid = $self->appid; my $pid = $self->pids->{profile_collection}; $self->set_header( 'Content-Length' => '0', ); my $resp = $self->put( "/oamp/configuration/objects/Profiles/$pid/provisions/Cached?appid=$appid&sync_key=0", ); return $resp; } sub download_route_table { my ($self) = @_; my $appid = $self->appid; my $pid = $self->pids->{route_table}; $self->set_header( 'Content-Length' => '0', ); my $resp = $self->put( "/oamp/configuration/objects/RouteTable/$pid/provisions/Cached?appid=$appid&sync_key=0", ); return $resp; } sub download_channel_groups { my ($self) = @_; my $appid = $self->appid; my $pid = $self->pids->{channel_group_collection}; $self->set_header( 'Content-Length' => '0', ); my $resp = $self->put( "/oamp/configuration/objects/ChannelGroups/$pid/provisions/Cached?appid=$appid&sync_key=0", ); return $resp; } #delete all children of bn2020 but not the node itself sub delete_all_bn2020 { my ($self) = @_; my $pid = $self->pids->{bn2020}; my $appid = $self->appid; my $resp = $self->get( "/oamp/configuration/objects?appid=$appid", ); if ($resp->code != 200) { warn "failed to get objects\n"; return $resp; } my $root = $resp->data->{object}[0]; #bn2020 for my $ch (_get_all_children($root)) { $self->_recursive_delete($ch); } return $resp; } sub _get_all_children { my $node = shift; if (defined $node->{object}) { if ("HASH" eq ref $node->{object}) { return ($node->{object},); } elsif ("ARRAY" eq ref $node->{object}) { return @{ $node->{object} }; } } return (); } sub _recursive_delete { my ($self, $node) = @_; my $classname = $node->{classname}; my $oid = $node->{oid}; my $appid = $self->appid; for my $ch (_get_all_children($node)) { $self->_recursive_delete($ch); } return if $classname eq "Node"; # don't delete the bn2020 object (causes reboot) my $path = "/oamp/configuration/objects/$classname/$oid?appid=$appid&sync_key=0"; $self->set_header( 'Content-Length' => '0', ); my $resp = $self->delete($path); #use DDP; p $path; p $resp->code; return; } sub reboot_and_wait { my ($self) = @_; my $appid = $self->appid; $self->set_header( 'Content-Length' => '0', ); # my $resp = $self->delete("/oamp/configuration/database/new?appid=$appid", # ''); my $resp = $self->_request_with_body("DELETE", "/oamp/configuration/database/new?appid=$appid", ''); if ($resp->code != 200) { warn "failed to reset config\n"; return $resp; } sleep 2; # not to catch the old server for (my $i = 0; $i < 100; $i++) { # 500 seconds on 5 seconds timeout $resp = $self->get("/"); last if $resp->code < 500; } return $resp; } # warning: does create a lot of open transactions without deleting them. # see the scriptfile for an example how to generate documentation of this sub build_documentation { my ($self) = @_; my $classinfo = $self->classinfo; for my $class (keys %{ $classinfo }) { my $classname = $classinfo->{$class}{name}; my $appid = $self->appid; my $pid = 10_000; my $resp = $self->get( "/oamp/configuration/objects/$classname/NULL?detaillevel=4&pid=$pid&appid=$appid", ); if ($resp->code != 200) { warn "$class: couldn't fetch info\n"; next; } my $data = $resp->data; my $options = []; for my $p ( keys %{ $data->{property} } ) { next if lc($data->{property}{$p}{type}) ne "configure"; my @choices; if ($data->{property}{$p}{choiceset}{choice} && ref $data->{property}{$p}{choiceset}{choice} eq "ARRAY") { for my $v (@{ $data->{property}{$p}{choiceset}{choice} }) { push @choices, $v->{value} =~ s/^value\((.*)\)$/$1/r; } } push @{ $options }, { name => $p, default => $data->{property}{$p}{value}, displayname => $data->{property}{$p}{displayname}, @choices ? (choices => [@choices]) : (), }; } $classinfo->{$class}{options} = $options; } return $classinfo; } sub _build_response_data { my ( $self, $req, $pid, $options ) = @_; my $resp = { pid => $pid, property => {}, }; for my $p ( keys %{ $req->{property} } ) { next if lc($req->{property}{$p}{type}) ne "configure"; $resp->{property}{$p} = { configuredvalue => $req->{property}{$p}{value} }; if ( defined $options->{$p} ) { $resp->{property}{$p}{configuredvalue} = $options->{$p}; } } return $resp; } sub _build_validation_data { my ( $self, $req, $pid, $options ) = @_; my $response_data = $self->_build_response_data($req, $pid, $options); my @changedopt = (); if ("HASH" eq ref $options && keys %{ $options }) { @changedopt = ( changedproperty => (keys %{ $options })[0], ); } my $resp = { pid => $pid, appid => $self->appid, @changedopt, }; for my $prop (keys %{ $response_data->{property} }) { $resp->{$prop} = $response_data->{property}{$prop}{configuredvalue}; } return $resp; } sub _build_user_agent { return HTTP::Tiny->new; } sub objects { return { bn2020 => { 'property' => { 'IPType' => { 'configuredvalue' => 'IPv4' }, '_position_' => { 'configuredvalue' => '' }, '_localDbSyncKey_' => { 'configuredvalue' => '0' }, 'PacketAudioChannels' => { 'configuredvalue' => 'Unknown' }, 'CFCMode' => { 'configuredvalue' => 'Unknown' }, 'srtpEnable' => { 'configuredvalue' => 'Disabled' }, 'InterfaceType' => { 'configuredvalue' => 'Unknown' }, 'MediaMode' => { 'configuredvalue' => 'Audio Dynamic Density Management' }, 'SwVersion' => { 'configuredvalue' => 'Unknown' }, 'SubNetMask' => { 'configuredvalue' => '' }, 'Name' => { 'configuredvalue' => 'Node0' }, 'ResendLogic' => { 'configuredvalue' => 'Disable' }, 'PacketMultimediaChannels' => { 'configuredvalue' => 'Unknown' }, 'NetworkMultimediaChannels' => { 'configuredvalue' => 'Unknown' }, 'IPAddress' => { 'configuredvalue' => '' }, 'ACLName' => { 'configuredvalue' => 'Unrestricted' }, 'NodeType' => { 'configuredvalue' => 'Unknown' }, 'ACL_ID' => { 'configuredvalue' => '0' }, 'IPChannels' => { 'configuredvalue' => 'Unknown' }, 'ID' => { 'configuredvalue' => '0' }, '_objectName_' => { 'configuredvalue' => 'BN2020: Node0 - ID: 0' }, 'LicIPChannels' => { 'configuredvalue' => '0' }, }, 'pid' => '10000', }, }; } 1; # vim: set tabstop=4 expandtab: