You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							339 lines
						
					
					
						
							9.5 KiB
						
					
					
				
			
		
		
	
	
							339 lines
						
					
					
						
							9.5 KiB
						
					
					
				| #!/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 // "<none>");
 | |
| 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 <YES|no>: ";
 | |
| my $confirm = <STDIN>;  ## 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
 |