#!/usr/bin/perl use strict; use warnings; use v5.14; use JSON qw(); use LWP::UserAgent; use IO::Socket::SSL; use IPC::Shareable; use Time::HiRes qw(usleep gettimeofday tv_interval); use Getopt::Long; use Pod::Usage; use Data::Dumper; my $user = 'administrator'; my $pass = 'administrator'; my $host = 'localhost'; my $port = 1443; my $domain = 'bench.demo-dev.sipwise.com'; my $reseller_id = 1; my $billprof_id = 1; my $customers = 50000; my $subs_per_customer = 1; my $customer_type = 'sipaccount'; my $uri_base = 'benchuser'; my $password = 'password'; my $number_cc = '43'; my $number_ac = '111'; my $procs = 8; my $certpath; my $help = undef; my $man = undef; GetOptions( "procs=i" => \$procs, "cert=s" => \$certpath, "api-user=s" => \$user, "api-pass=s" => \$pass, "api-host=s" => \$host, "api-port=i" => \$port, "reseller-id=i" => \$reseller_id, "billprof-id=i" => \$billprof_id, "domain=s" => \$domain, "customers=i" => \$customers, "subs-per-customer=i" => \$subs_per_customer, "customer-type=s" => \$customer_type, "uri-base=s" => \$uri_base, "sip-password=s" => \$password, "number-cc=i" => \$number_cc, "number-ac=i" => \$number_ac, "help|?" => \$help, "man" => \$man, ) or pod2usage(2); pod2usage(1) if $help; pod2usage(-exitval => 0, -verbose => 2) if $man; say "Running with the following config:"; say " procs=$procs"; say " cert=" . ($certpath // ""); say " api-user=$user"; say " api-pass=$pass"; say " api-host=$host"; say " api-port=$port"; say " reseller-id=$reseller_id"; say " billprof-id=$billprof_id"; say " domain=$domain"; say " customers=$customers"; say " subs-per-customer=$subs_per_customer"; say " customer-type=$customer_type"; say " uri-base=$uri_base"; say " sip-password=$password"; say " number-cc=$number_cc"; say " number-ac=$number_ac"; print "Proceed : "; my $confirm = ; ## no critic (ProhibitExplicitStdin) chomp $confirm; if($confirm eq "" || lc($confirm) eq "yes") { say "Proceeding as requested."; } else { die "Aborting as requested.\n"; } sub work; # our shared counters my $count_customers; my $handle_customers = tie $count_customers, 'IPC::Shareable', undef, { destroy => 1 }; $count_customers = 0; my $count_kids_done; my $handle_kids_done = tie $count_kids_done, 'IPC::Shareable', undef, { destroy => 1 }; $count_kids_done = 0; my $has_error; my $handle_error = tie $has_error, 'IPC::Shareable', undef, { destroy => 1 }; $has_error = 0; my $chunk_size = int($customers/$procs); my $chunk_rest = $customers % $procs; my $last_chunk_size; if($chunk_rest > $procs) { $chunk_size += int($chunk_rest/$procs); $last_chunk_size = $chunk_size + ($chunk_rest % $procs); } else { $last_chunk_size = $chunk_size + ($customers % $procs); } my $urlbase = "https://$host:$port"; sub create_ua { my $ua = LWP::UserAgent->new(keep_alive => 1); $ua->ssl_opts( verify_hostname => 0, SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, $certpath ? ( SSL_cert_file => $certpath, SSL_key_file => $certpath, ) : (), ); $ua->credentials("$host:$port", 'api_admin_http', $user, $pass); return $ua; } my ($req, $res); my $domain_id; my $ua = create_ua(); $req = HTTP::Request->new('GET', "$urlbase/api/domains/?domain=$domain"); $res = $ua->request($req); if($res->is_success) { my $collection = JSON::from_json($res->decoded_content); if($collection->{total_count} == 0) { say "Domain '$domain' not found, creating..."; $req = HTTP::Request->new('POST', "$urlbase/api/domains/"); $req->content_type('application/json'); $req->content(JSON::to_json({ reseller_id => $reseller_id, domain => $domain, })); $res = $ua->request($req); if($res->is_success) { say "Domain '$domain' created, proceeding..."; $domain_id = $res->header('Location'); $domain_id =~ s/^.+\/(\d+)$/$1/; } else { die "Failed to create domain '$domain': " . $res->status_line . "\n"; } } else { my $col = $collection->{_embedded}->{'ngcp:domains'}; if(ref $col eq "HASH") { $domain_id = $col->{id}; } elsif(ref $col eq "ARRAY") { $domain_id = $col->[0]->{id}; } else { die "Invalid HAL response when fetching domain '$domain':\n" . (Dumper $col) . "\n"; } } } else { die "Failed to fetch fetch domain id: " . $res->status_line . "\n"; } say "Domain '$domain' has id $domain_id"; my @kids = (); my $chunk_start = 0; my $proc_chunk_first = 0; my $proc_chunk_last = 0; say "chunk size is $chunk_size, last chunk size is $last_chunk_size"; my $t0 = [gettimeofday]; my $pid; for(my $i = 0; $i < $procs; ++$i) { if($i == $procs-1) { $proc_chunk_last = $proc_chunk_first + $last_chunk_size - 1; } else { $proc_chunk_last = $proc_chunk_first + $chunk_size - 1; } unless($pid = fork()) { die "Failed to fork worker process: $!\n" unless defined $pid; work(); exit; } push @kids, $pid; $proc_chunk_first = $proc_chunk_last + 1; } my $last_customers = 0; while($count_kids_done < $procs && !$has_error) { my $customers_psec = $count_customers - $last_customers; say sprintf('%05.2f', ($count_customers / $customers * 100.0))."% at $customers_psec/sec"; $last_customers = $count_customers; sleep 1; } if($has_error) { kill 'TERM', @kids; } say "Processing done, collecting final status..."; if($pid > 0) { my $last_kids = @kids; while((my $child_pid = wait()) > 0 && @kids) { @kids = grep { !/^$child_pid$/ } @kids; if(@kids == $last_kids) { sleep 1; } $last_kids = @kids; } } if($has_error) { say "Processing has failed with errors!"; } else { say "Done creating $count_customers customers"; my $duration = tv_interval($t0); say "Overall duration was $duration with " . ($customers/$duration) . " customers per sec and " . ($duration/$customers) . " sec per customer"; } sub mydie { my $msg = shift; $has_error = 1; die $msg; } sub work { $ua = create_ua(); # TODO: make it in chunks, since other procs are doing the same! for(my $i = $proc_chunk_first; $i <= $proc_chunk_last; ++$i) { $handle_customers->shlock(); $count_customers++; $handle_customers->shunlock(); my ($contact_id, $customer_id, $subscriber_id); $req = HTTP::Request->new('POST', "$urlbase/api/customercontacts/"); $req->header('Content-Type' => 'application/json'); $req->content(JSON::to_json({ reseller_id => $reseller_id, email => sprintf('customer%06d@sipwise.internal', $i), })); $res = $ua->request($req); if($res->is_success) { $contact_id = $res->header('Location'); $contact_id =~ s/^.*\/(\d+)$/$1/; } else { mydie("Failed to create customer contact: " . $res->status_line . "\n"); } $req = HTTP::Request->new('POST', "$urlbase/api/customers/"); $req->header('Content-Type' => 'application/json'); $req->content(JSON::to_json({ billing_profile_id => $billprof_id, contact_id => $contact_id, external_id => $i, status => 'active', type => $customer_type, })); $res = $ua->request($req); if($res->is_success) { $customer_id = $res->header('Location'); $customer_id =~ s/^.*\/(\d+)$/$1/; } else { mydie("Failed to create customer: " . $res->status_line . "\n"); } for(my $j = 0; $j < $subs_per_customer; ++$j) { $req = HTTP::Request->new('POST', "$urlbase/api/subscribers/"); $req->header('Content-Type' => 'application/json'); $req->content(JSON::to_json({ customer_id => $customer_id, domain_id => $domain_id, password => $password, primary_number => { cc => $number_cc, ac => $number_ac, sn => sprintf('%06d', $i).sprintf('%03d', $j) }, status => 'active', username => $uri_base.sprintf('%06d', $i).sprintf('%03d', $j), })); $res = $ua->request($req); if($res->is_success) { $subscriber_id = $res->header('Location'); $subscriber_id =~ s/^.*\/(\d+)$/$1/; } else { mydie("Failed to create subscriber: " . $res->status_line . "\n"); } } } $handle_kids_done->shlock(); $count_kids_done++; $handle_kids_done->shunlock(); } __END__ =head1 NAME ngcp-create-testusers =head1 DESCRIPTION Optimized batch-creation for test customers/subscribers =head1 SYNOPSIS ngcp-create-testusers [options] Options: --procs=i --api-user=s --api-pass=s --api-host=s --api-port=i --reseller-id=i --billprof-id=i --domain=s --customers=i --subs-per-customer=i --customer-type=s --uri-base=s --sip-password=s --number-cc=i --number-ac=i =head1 AUTHOR Sipwise Development Team =head1 LICENSE This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut