Changeset 702


Ignore:
Timestamp:
02/23/15 18:16:11 (9 years ago)
Author:
Kris Deugau
Message:

/trunk

Add "split block" feature. See #7. May still need a little tweaking
("List IPs" link for pools, refiddle rDNS template records?)

Also stubbed out "shrink block" (branch for "split block"), and added
a placeholder for "merge blocks".

Location:
trunk
Files:
2 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/cgi-bin/IPDB.pm

    r699 r702  
    3232        &getMasterList &getTypeList &getPoolSelect &findAllocateFrom
    3333        &ipParent &subParent &blockParent &getBreadCrumbs &getRoutedCity
    34         &allocateBlock &updateBlock &deleteBlock &getBlockData &getBlockRDNS &getRDNSbyIP
     34        &allocateBlock &updateBlock &splitBlock &deleteBlock &getBlockData &getBlockRDNS &getRDNSbyIP
    3535        &getNodeList &getNodeName &getNodeInfo
    3636        &mailNotify
     
    4747                &getMasterList &getTypeList &getPoolSelect &findAllocateFrom
    4848                &ipParent &subParent &blockParent &getBreadCrumbs &getRoutedCity
    49                 &allocateBlock &updateBlock &deleteBlock &getBlockData &getBlockRDNS &getRDNSbyIP
     49                &allocateBlock &updateBlock &splitBlock &deleteBlock &getBlockData &getBlockRDNS &getRDNSbyIP
    5050                &getNodeList &getNodeName &getNodeInfo
    5151                &mailNotify
     
    14531453
    14541454
     1455## IPDB::splitBlock()
     1456# Splits an existing allocation into two or more smaller allocations based on a passed netmask
     1457# Duplicates all other data
     1458# Returns an arrayref to a list of hashrefs with ID and CIDR keys for the list of new allocations.
     1459# Should probably commit DNS magic to realign DNS data
     1460sub splitBlock {
     1461  my $dbh = shift;
     1462  my $id = shift;
     1463  my $basetype = shift;
     1464  my $newmask = shift;
     1465
     1466##fixme:  set errstr on errors so caller can suitably clue-by-four the user
     1467  return if $basetype ne 'b';  # only netblocks allowed!
     1468
     1469  my $binfo = getBlockData($dbh, $id);
     1470  return if !$binfo;
     1471
     1472  return if $newmask !~ /^\d+$/;
     1473
     1474  my @ret;
     1475  my $block = new NetAddr::IP $binfo->{block};
     1476  my $oldmask = $block->masklen;
     1477
     1478  # Fail if the block to split is "too small" - eg, can't split a v4 /32 at all
     1479  # failure modes:
     1480  # difference between $oldmask and $newmask is negative or 0
     1481  if ($newmask - $oldmask <= 0) {
     1482    $errstr = "Can't split a /$oldmask allocation into /$newmask pieces";
     1483    return;
     1484  }
     1485#  # difference between $oldmask and $newmask is > n, for arbitrary n?
     1486#  if ($newmask - $oldmask > 42) {  # because 42
     1487#  }
     1488  # $oldmask > n, for arbitrary n?  At least check limits of data type.
     1489  if ($block->{isv6}) {
     1490    if ($newmask - $oldmask > 128) {
     1491      $errstr = "Impossible IPv6 mask length /$newmask requested";
     1492      return;
     1493    }
     1494  } else {
     1495    if ($newmask - $oldmask > 32) {
     1496      $errstr = "Impossible IPv4 mask length /$newmask requested";
     1497      return;
     1498    }
     1499  }
     1500
     1501  my @newblocks = $block->split($newmask);
     1502
     1503  local $dbh->{AutoCommit} = 0;
     1504  local $dbh->{RaiseError} = 1;
     1505
     1506  eval {
     1507    # line up a list of fields and values.  Be nice if there was a handy way to do,
     1508    # direct in SQL, something like
     1509    # "INSERT INTO foo (f1,f2,f3) VALUES (newf1,(SELECT oldf2,oldf3 FROM foo WHERE baz))"
     1510    my @fieldlist = qw(type city description notes circuitid privdata custid swip vrf vlan rdns parent_id master_id);
     1511    my $fields_sql = join(',', @fieldlist);
     1512    my @vals;
     1513    foreach (@fieldlist) {
     1514      push @vals, $binfo->{$_};
     1515    }
     1516    # note the first block in the split for return
     1517    push @ret, {nid => $id, nblock => "$newblocks[0]"};
     1518
     1519    # prepare
     1520    my $idsth = $dbh->prepare("SELECT currval('allocations_id_seq')");
     1521    my $poolsth = $dbh->prepare("INSERT INTO allocations (cidr, $fields_sql)".
     1522        " VALUES (?".',?'x(scalar(@fieldlist)).")");
     1523    my $poolchildsth = $dbh->prepare("UPDATE poolips SET parent_id = ? WHERE ip << ? AND parent_id = ?");
     1524    my $nbsth = $dbh->prepare("DELETE FROM poolips WHERE parent_id = ? AND ip = ?");
     1525
     1526    # set up update of existing block
     1527    $dbh->do("UPDATE allocations SET cidr = ? WHERE id = ?", undef, ("$newblocks[0]", $id) );
     1528
     1529    # axe the net, gw, and bcast IPs as necessary when splitting a "normal" pool
     1530    if ($binfo->{type} =~ /.d/) {
     1531      $newblocks[0]--;
     1532      $nbsth->execute($id, $newblocks[0]->addr);
     1533    }
     1534
     1535    # Loop over the new blocks that are not the base block
     1536    for (my $i = 1; $i <= $#newblocks; $i++) {
     1537      # add the new pool
     1538      $poolsth->execute($newblocks[$i], @vals);
     1539      # fetch the ID of the entry we just added...
     1540      $idsth->execute();
     1541      my ($nid) = $idsth->fetchrow_array();
     1542      # ... so we can pass back the list of blocks and IDs...
     1543      push @ret, {nid => $nid, nblock => "$newblocks[$i]"};
     1544      # axe the net, gw, and bcast IPs as necessary when splitting a "normal" pool
     1545      if ($binfo->{type} =~ /.d/) {
     1546        # net
     1547        $nbsth->execute($id, $newblocks[$i]->addr);
     1548        $newblocks[$i]++;
     1549        # gw
     1550        $nbsth->execute($id, $newblocks[$i]->addr);
     1551        $newblocks[$i]--;
     1552        $newblocks[$i]--;
     1553        # bcast
     1554        $nbsth->execute($id, $newblocks[$i]->addr);
     1555        $newblocks[$i]++;
     1556      }
     1557      # ... and update the existing IPs with the new parent_id
     1558      $poolchildsth->execute($nid, $newblocks[$i], $id);
     1559    }
     1560
     1561    $dbh->commit;
     1562  };
     1563  if ($@) {
     1564    $errstr = "Error splitting $binfo->{block}: $@";
     1565    $dbh->rollback;
     1566    return;
     1567  }
     1568
     1569  return \@ret;
     1570} # end splitBlock()
     1571
     1572
    14551573## IPDB::deleteBlock()
    14561574# Removes an allocation from the database, including deleting IPs
  • trunk/cgi-bin/main.cgi

    r697 r702  
    171171elsif($webvar{action} eq 'update') {
    172172  update();
     173}
     174elsif($webvar{action} eq 'split') {
     175  prepSplit();
     176}
     177elsif($webvar{action} eq 'dosplit') {
     178  doSplit();
    173179}
    174180elsif($webvar{action} eq 'delete') {
     
    965971  $page->param(maydel => $IPDBacl{$authuser} =~ /d/);
    966972
     973  # Need to find internal knobs to twist to actually vary these.  (Ab)use "change" flag for now
     974  $page->param(maymerge => ($IPDBacl{$authuser} =~ /c/ && $blockinfo->{type} !~ /^.i$/));
     975  if ($IPDBacl{$authuser} =~ /c/ && $blockinfo->{type} !~ /^.i$/) {
     976    if ($blockinfo->{type} =~ /^.p$/) {
     977      # PPP pools
     978      $page->param(maysplit => 1) if $cidr->masklen+1 < $cidr->bits;
     979    } elsif ($blockinfo->{type} =~ /.d/) {
     980      # Non-PPP pools
     981      $page->param(maysplit => 1) if $cidr->masklen+2 < $cidr->bits;
     982    } else {
     983      # Standard netblocks.  Arguably allowing splitting these down to single IPs
     984      # doesn't make much sense, but forcing users to apply allocation types
     985      # "properly" is worse than herding cats.
     986      $page->param(maysplit => 1) if $cidr->masklen < $cidr->bits;
     987    }
     988  }
     989
    967990} # edit()
    968991
     
    10611084
    10621085
     1086sub prepSplit {
     1087  if ($IPDBacl{$authuser} !~ /c/) {
     1088    $aclerr = 'splitblock';
     1089    return;
     1090  }
     1091
     1092  my $blockinfo = getBlockData($ip_dbh, $webvar{block});
     1093
     1094  if ($blockinfo->{type} =~ /^.i$/) {
     1095    $page->param(err => "Can't split a single IP allocation");
     1096    return;
     1097  }
     1098
     1099  # Info about current allocation
     1100  $page->param(oldblock => $blockinfo->{block});
     1101  $page->param(block => $webvar{block});
     1102
     1103# Note that there are probably different rules that should be followed to restrict splitting IPv6 blocks;
     1104# strictly speaking it will be exceptionally rare to see smaller than a /64 assigned to a customer, since that
     1105# breaks auto-addressing schemes.
     1106
     1107  # Generate possible splits
     1108  my $block = new NetAddr::IP $blockinfo->{block};
     1109  my $oldmask = $block->masklen;
     1110  if ($blockinfo->{type} =~ /^.d$/) {
     1111    # Non-PPP pools
     1112    if ($oldmask+2 >= $block->bits) {
     1113      $page->param(err => "Can't split a standard netblock pool any further");
     1114      return;
     1115    }
     1116    # Allow splitting down to v4 /30 (which results in one usable IP;  dubiously useful)
     1117    $page->param(sp4mask => $oldmask+2) if $oldmask+2 <= $block->bits-2;
     1118  } elsif ($blockinfo->{type} =~ /.p/) {
     1119    # Allow splitting PPP pools down to v4 /31
     1120    $page->param(sp4mask => $oldmask+2) if $oldmask+2 <= $block->bits-1;
     1121  } else {
     1122    # Allow splitting all other non-pool netblocks down to single IPs, which...
     1123    # arguably should be *aggregated* in a pool.  Except where they shouldn't.
     1124    $page->param(sp4mask => $oldmask+2) if $oldmask+2 <= $block->bits;
     1125  }
     1126  # set the split-in-half mask
     1127  $page->param(sp2mask => $oldmask+1);
     1128
     1129  # Generate possible shrink targets
     1130  my @keepers = $block->split($block->masklen+1);
     1131  $page->param(newblockA => $keepers[0]);
     1132  $page->param(newblockB => $keepers[1]);
     1133} # prepSplit()
     1134
     1135
     1136sub doSplit {
     1137  if ($IPDBacl{$authuser} !~ /c/) {
     1138    $aclerr = 'splitblock';
     1139    return;
     1140  }
     1141
     1142##fixme:  need consistent way to identify "this thing that is this thing" with only the ID
     1143# also applies to other locations
     1144  my $blockinfo = getBlockData($ip_dbh, $webvar{block});
     1145
     1146  if ($blockinfo->{type} =~ /^.i$/) {
     1147    $page->param(err => "Can't split a single IP allocation");
     1148    return;
     1149  }
     1150
     1151  if ($webvar{subact} eq 'split') {
     1152    $page->param(issplit => 1);
     1153    my $binfo = getBlockData($ip_dbh, $webvar{block});
     1154    $page->param(cidr => $binfo->{block});
     1155    my $block = new NetAddr::IP $binfo->{block};
     1156    my $newblocks = splitBlock($ip_dbh, $webvar{block}, 'b', $webvar{split});
     1157    if ($newblocks) {
     1158      $page->param(newblocks => $newblocks);
     1159      # and the backlink to the parent container
     1160      my $pinfo = getBlockData($ip_dbh, $binfo->{parent_id});
     1161      $page->param(backid => $binfo->{parent_id});
     1162      $page->param(backblock => $pinfo->{block});
     1163    } else {
     1164      $page->param(err => $IPDB::errstr);
     1165    }
     1166
     1167  } else {
     1168    # Shrink
     1169  }
     1170} # doSplit()
     1171
     1172
    10631173# Delete an allocation.
    10641174sub remove {
  • trunk/templates/edit.tmpl

    r695 r702  
    55
    66<tr>
    7 <td>
     7<td colspan="3">
    88
    99<TMPL_IF maychange>
     
    221221</TMPL_IF>
    222222
     223</td></tr>
     224<tr>
     225<td class="center">
     226
     227<div class="revdata">
     228
     229<TMPL_IF maymerge>
     230<form method="POST" action="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi">
     231<fieldset><legend class="noshow">&nbsp;</legend>
     232<input type="hidden" name="action" value="merge">
     233<input type="hidden" name="block" value="<TMPL_VAR NAME=id>">
     234<input type="hidden" name="basetype" value="<TMPL_VAR NAME=basetype>">
     235<div class="button_l">
     236<input type="submit" value=" Merge with adjacent block(s) ">
     237</div>
     238</fieldset>
     239</form>
     240</TMPL_IF>
     241
    223242<TMPL_IF maydel>
    224 </td></tr>
    225 <tr><td class="center">
    226 <form method="POST" action="main.cgi">
    227 <fieldset><legend class="noshow">&nbsp;</legend>
    228 <div class="revdata">
     243<form method="POST" action="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi">
     244<fieldset><legend class="noshow">&nbsp;</legend>
    229245<input type="hidden" name="action" value="delete">
    230246<input type="hidden" name="block" value="<TMPL_VAR NAME=id>">
    231247<input type="hidden" name="basetype" value="<TMPL_VAR NAME=basetype>">
    232248<input type=submit value=" Delete this block ">
    233 </div>
    234 </fieldset>
    235 </form>
    236 </TMPL_IF>
    237 
    238 </td>
     249</fieldset>
     250</form>
     251</TMPL_IF>
     252
     253<TMPL_IF maysplit>
     254<form method="POST" action="<TMPL_VAR NAME=webpath>/cgi-bin/main.cgi">
     255<fieldset><legend class="noshow">&nbsp;</legend>
     256<input type="hidden" name="action" value="split">
     257<input type="hidden" name="block" value="<TMPL_VAR NAME=id>">
     258<input type="hidden" name="basetype" value="<TMPL_VAR NAME=basetype>">
     259<div class="button_r">
     260<input type=submit value=" Split/shrink this block ">
     261</div>
     262</fieldset>
     263</form>
     264</TMPL_IF>
     265
     266</div>
     267
     268</td>
     269
    239270</tr>
    240271</table>
Note: See TracChangeset for help on using the changeset viewer.