function resolve8021QConflicts() { global $sic, $dbxlink; assertUIntArg('mutex_rev', TRUE); // counts from 0 assertUIntArg('nrows'); // Divide submitted radio buttons into 3 groups: // left (saved version wins) // asis (ignore) // right (running version wins) $F = array(); for ($i = 0; $i < $sic['nrows']; $i++) { if (!array_key_exists("i_{$i}", $sic)) { continue; } // let's hope other inputs are in place switch ($sic["i_{$i}"]) { case 'left': case 'right': $F[$sic["pn_{$i}"]] = array('mode' => $sic["rm_{$i}"], 'allowed' => array_key_exists("ra_{$i}", $sic) ? $sic["ra_{$i}"] : array(), 'native' => $sic["rn_{$i}"], 'decision' => $sic["i_{$i}"]); break; default: // don't care } } $dbxlink->beginTransaction(); try { if (NULL === ($vswitch = getVLANSwitchInfo($sic['object_id'], 'FOR UPDATE'))) { throw new InvalidArgException('object_id', $sic['object_id'], 'VLAN domain is not set for this object'); } if ($vswitch['mutex_rev'] != $sic['mutex_rev']) { throw new InvalidRequestArgException('mutex_rev', $sic['mutex_rev'], 'expired form (table data has changed)'); } $D = getStored8021QConfig($vswitch['object_id'], 'desired'); $C = getStored8021QConfig($vswitch['object_id'], 'cached'); $R = getRunning8021QConfig($vswitch['object_id']); $plan = get8021QSyncOptions($vswitch, $D, $C, $R['portdata']); $ndone = 0; foreach ($F as $port_name => $port) { if (!array_key_exists($port_name, $plan)) { continue; } elseif ($plan[$port_name]['status'] == 'merge_conflict') { // for R neither mutex nor revisions can be emulated, but revision change can be if (!same8021QConfigs($port, $R['portdata'][$port_name])) { throw new InvalidRequestArgException("port {$port_name}", '(hidden)', 'expired form (switch data has changed)'); } if ($port['decision'] == 'right') { upd8021QPort('cached', $vswitch['object_id'], $port_name, $port); $ndone++; } elseif ($port['decision'] == 'left') { upd8021QPort('cached', $vswitch['object_id'], $port_name, $D[$port_name]); $ndone++; } // otherwise there was no decision made } elseif ($plan[$port_name]['status'] == 'delete_conflict' or $plan[$port_name]['status'] == 'martian_conflict') { if ($port['decision'] == 'left') { // confirm deletion of local copy del8021QPort($vswitch['object_id'], $port_name); $ndone++; } } // otherwise ignore a decision, which doesn't address a conflict } } catch (InvalidRequestArgException $e) { $dbxlink->rollBack(); return showFuncMessage(__FUNCTION__, 'ERR1'); } catch (Exception $e) { $dbxlink->rollBack(); return showFuncMessage(__FUNCTION__, 'ERR2'); } $dbxlink->commit(); return showFuncMessage(__FUNCTION__, 'OK', array($ndone)); }
function update8021QPortList() { genericAssertion('ports', 'array'); $enabled = $disabled = 0; global $sic; $default_port = array('mode' => 'access', 'allowed' => array(VLAN_DFL_ID), 'native' => VLAN_DFL_ID); foreach ($sic['ports'] as $line) { if (preg_match('/^enable (.+)$/', $line, $m)) { $enabled += add8021QPort(getBypassValue(), $m[1], $default_port); } elseif (preg_match('/^disable (.+)$/', $line, $m)) { $disabled += del8021QPort(getBypassValue(), $m[1]); } else { throw new InvalidRequestArgException('ports[]', $line, 'malformed array item'); } } # $enabled + $disabled > 0 if ($enabled) { showSuccess("enabled 802.1Q for {$enabled} port(s)"); } if ($disabled) { showSuccess("disabled 802.1Q for {$disabled} port(s)"); } }
function exec8021QDeploy($object_id, $do_push) { global $dbxlink; $nsaved = $npushed = $nsaved_uplinks = 0; $dbxlink->beginTransaction(); if (NULL === ($vswitch = getVLANSwitchInfo($object_id, 'FOR UPDATE'))) { throw new InvalidArgException('object_id', $object_id, 'VLAN domain is not set for this object'); } $D = getStored8021QConfig($vswitch['object_id'], 'desired'); $C = getStored8021QConfig($vswitch['object_id'], 'cached'); try { $R = getRunning8021QConfig($vswitch['object_id']); } catch (RTGatewayError $e) { usePreparedExecuteBlade('UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?', array(E_8021Q_PULL_REMOTE_ERROR, $vswitch['object_id'])); $dbxlink->commit(); return 0; } $conflict = FALSE; $ok_to_push = array(); foreach (get8021QSyncOptions($vswitch, $D, $C, $R['portdata']) as $pn => $port) { // always update cache with new data from switch switch ($port['status']) { case 'ok_to_merge': // FIXME: this can be logged upd8021QPort('cached', $vswitch['object_id'], $pn, $port['both']); break; case 'ok_to_delete': del8021QPort($vswitch['object_id'], $pn); $nsaved++; break; case 'ok_to_add': add8021QPort($vswitch['object_id'], $pn, $port['right']); $nsaved++; break; case 'delete_conflict': case 'merge_conflict': case 'add_conflict': case 'martian_conflict': $conflict = TRUE; break; case 'ok_to_pull': // FIXME: this can be logged upd8021QPort('desired', $vswitch['object_id'], $pn, $port['right']); upd8021QPort('cached', $vswitch['object_id'], $pn, $port['right']); $nsaved++; break; case 'ok_to_push_with_merge': upd8021QPort('cached', $vswitch['object_id'], $pn, $port['right']); // fall through // fall through case 'ok_to_push': $ok_to_push[$pn] = $port['left']; break; } } // redo uplinks unconditionally $domain_vlanlist = getDomainVLANs($vswitch['domain_id']); $Dnew = apply8021QOrder($vswitch['template_id'], getStored8021QConfig($vswitch['object_id'], 'desired')); // Take new "desired" configuration and derive uplink port configuration // from it. Then cancel changes to immune VLANs and save resulting // changes (if any left). $new_uplinks = filter8021QChangeRequests($domain_vlanlist, $Dnew, produceUplinkPorts($domain_vlanlist, $Dnew, $vswitch['object_id'])); $nsaved_uplinks += replace8021QPorts('desired', $vswitch['object_id'], $Dnew, $new_uplinks); if ($nsaved + $nsaved_uplinks) { // saved configuration has changed (either "user" ports have changed, // or uplinks, or both), so bump revision number up) touchVLANSwitch($vswitch['object_id']); } if ($conflict) { usePreparedExecuteBlade('UPDATE VLANSwitch SET out_of_sync="yes", last_errno=?, last_error_ts=NOW() WHERE object_id=?', array(E_8021Q_VERSION_CONFLICT, $vswitch['object_id'])); } else { usePreparedExecuteBlade('UPDATE VLANSwitch SET last_errno=?, last_error_ts=NOW() WHERE object_id=?', array(E_8021Q_NOERROR, $vswitch['object_id'])); // Modified uplinks are very likely to differ from those in R-copy, // so don't mark device as clean, if this happened. This can cost // us an additional, empty round of sync, but at least out_of_sync // won't be mistakenly set to 'no'. // FIXME: A cleaner way of coupling pull and push operations would // be to split this function into two. if (!count($ok_to_push) and !$nsaved_uplinks) { usePreparedExecuteBlade('UPDATE VLANSwitch SET out_of_sync="no" WHERE object_id=?', array($vswitch['object_id'])); } elseif ($do_push) { usePreparedExecuteBlade('UPDATE VLANSwitch SET last_push_started=NOW() WHERE object_id=?', array($vswitch['object_id'])); try { $vlan_names = isset($R['vlannames']) ? $R['vlannames'] : array(); $npushed += exportSwitch8021QConfig($vswitch, $R['vlanlist'], $R['portdata'], $ok_to_push, $vlan_names); // update cache for ports deployed replace8021QPorts('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push); usePreparedExecuteBlade('UPDATE VLANSwitch SET last_push_finished=NOW(), out_of_sync="no", last_errno=? WHERE object_id=?', array(E_8021Q_NOERROR, $vswitch['object_id'])); } catch (RTGatewayError $r) { usePreparedExecuteBlade('UPDATE VLANSwitch SET out_of_sync="yes", last_error_ts=NOW(), last_errno=? WHERE object_id=?', array(E_8021Q_PUSH_REMOTE_ERROR, $vswitch['object_id'])); callHook('pushErrorHandler', $object_id, $r); } } } $dbxlink->commit(); // start downlink work only after unlocking current object to make deadlocks less likely to happen // TODO: only process changed uplink ports if ($nsaved_uplinks) { initiateUplinksReverb($vswitch['object_id'], $new_uplinks); } return $nsaved + $npushed + $nsaved_uplinks; }