Merge branch 'master' of /home/q3k/Projects/hscloud/go/src/code.hackerspace.pl/q3k/topo
commit
ff9995ce61
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,331 @@
|
||||||
|
/*
|
||||||
|
Viz.js 2.0.0 (Graphviz 2.40.1, Expat 2.2.5, Emscripten 1.37.36)
|
||||||
|
Copyright (c) 2014-2018 Michael Daines
|
||||||
|
Licensed under MIT license
|
||||||
|
|
||||||
|
This distribution contains other software in object code form:
|
||||||
|
|
||||||
|
Graphviz
|
||||||
|
Licensed under Eclipse Public License - v 1.0
|
||||||
|
http://www.graphviz.org
|
||||||
|
|
||||||
|
Expat
|
||||||
|
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd and Clark Cooper
|
||||||
|
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Expat maintainers.
|
||||||
|
Licensed under MIT license
|
||||||
|
http://www.libexpat.org
|
||||||
|
|
||||||
|
zlib
|
||||||
|
Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
|
||||||
|
http://www.zlib.net/zlib_license.html
|
||||||
|
*/
|
||||||
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||||||
|
typeof define === 'function' && define.amd ? define(factory) :
|
||||||
|
(global.Viz = factory());
|
||||||
|
}(this, (function () { 'use strict';
|
||||||
|
|
||||||
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||||
|
return typeof obj;
|
||||||
|
} : function (obj) {
|
||||||
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
var classCallCheck = function (instance, Constructor) {
|
||||||
|
if (!(instance instanceof Constructor)) {
|
||||||
|
throw new TypeError("Cannot call a class as a function");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var createClass = function () {
|
||||||
|
function defineProperties(target, props) {
|
||||||
|
for (var i = 0; i < props.length; i++) {
|
||||||
|
var descriptor = props[i];
|
||||||
|
descriptor.enumerable = descriptor.enumerable || false;
|
||||||
|
descriptor.configurable = true;
|
||||||
|
if ("value" in descriptor) descriptor.writable = true;
|
||||||
|
Object.defineProperty(target, descriptor.key, descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (Constructor, protoProps, staticProps) {
|
||||||
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||||
|
if (staticProps) defineProperties(Constructor, staticProps);
|
||||||
|
return Constructor;
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
var _extends = Object.assign || function (target) {
|
||||||
|
for (var i = 1; i < arguments.length; i++) {
|
||||||
|
var source = arguments[i];
|
||||||
|
|
||||||
|
for (var key in source) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
var WorkerWrapper = function () {
|
||||||
|
function WorkerWrapper(worker) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
classCallCheck(this, WorkerWrapper);
|
||||||
|
|
||||||
|
this.worker = worker;
|
||||||
|
this.listeners = [];
|
||||||
|
this.nextId = 0;
|
||||||
|
|
||||||
|
this.worker.addEventListener('message', function (event) {
|
||||||
|
var id = event.data.id;
|
||||||
|
var error = event.data.error;
|
||||||
|
var result = event.data.result;
|
||||||
|
|
||||||
|
_this.listeners[id](error, result);
|
||||||
|
delete _this.listeners[id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createClass(WorkerWrapper, [{
|
||||||
|
key: 'render',
|
||||||
|
value: function render(src, options) {
|
||||||
|
var _this2 = this;
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var id = _this2.nextId++;
|
||||||
|
|
||||||
|
_this2.listeners[id] = function (error, result) {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error(error.message, error.fileName, error.lineNumber));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
_this2.worker.postMessage({ id: id, src: src, options: options });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return WorkerWrapper;
|
||||||
|
}();
|
||||||
|
|
||||||
|
var ModuleWrapper = function ModuleWrapper(module, render) {
|
||||||
|
classCallCheck(this, ModuleWrapper);
|
||||||
|
|
||||||
|
var instance = module();
|
||||||
|
this.render = function (src, options) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
try {
|
||||||
|
resolve(render(instance, src, options));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
|
||||||
|
|
||||||
|
function b64EncodeUnicode(str) {
|
||||||
|
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
|
||||||
|
return String.fromCharCode('0x' + p1);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultScale() {
|
||||||
|
if ('devicePixelRatio' in window && window.devicePixelRatio > 1) {
|
||||||
|
return window.devicePixelRatio;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function svgXmlToImageElement(svgXml) {
|
||||||
|
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
||||||
|
_ref$scale = _ref.scale,
|
||||||
|
scale = _ref$scale === undefined ? defaultScale() : _ref$scale,
|
||||||
|
_ref$mimeType = _ref.mimeType,
|
||||||
|
mimeType = _ref$mimeType === undefined ? "image/png" : _ref$mimeType,
|
||||||
|
_ref$quality = _ref.quality,
|
||||||
|
quality = _ref$quality === undefined ? 1 : _ref$quality;
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var svgImage = new Image();
|
||||||
|
|
||||||
|
svgImage.onload = function () {
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = svgImage.width * scale;
|
||||||
|
canvas.height = svgImage.height * scale;
|
||||||
|
|
||||||
|
var context = canvas.getContext("2d");
|
||||||
|
context.drawImage(svgImage, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
canvas.toBlob(function (blob) {
|
||||||
|
var image = new Image();
|
||||||
|
image.src = URL.createObjectURL(blob);
|
||||||
|
image.width = svgImage.width;
|
||||||
|
image.height = svgImage.height;
|
||||||
|
|
||||||
|
resolve(image);
|
||||||
|
}, mimeType, quality);
|
||||||
|
};
|
||||||
|
|
||||||
|
svgImage.onerror = function (e) {
|
||||||
|
var error;
|
||||||
|
|
||||||
|
if ('error' in e) {
|
||||||
|
error = e.error;
|
||||||
|
} else {
|
||||||
|
error = new Error('Error loading SVG');
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
svgImage.src = 'data:image/svg+xml;base64,' + b64EncodeUnicode(svgXml);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function svgXmlToImageElementFabric(svgXml) {
|
||||||
|
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
||||||
|
_ref2$scale = _ref2.scale,
|
||||||
|
scale = _ref2$scale === undefined ? defaultScale() : _ref2$scale,
|
||||||
|
_ref2$mimeType = _ref2.mimeType,
|
||||||
|
mimeType = _ref2$mimeType === undefined ? 'image/png' : _ref2$mimeType,
|
||||||
|
_ref2$quality = _ref2.quality,
|
||||||
|
quality = _ref2$quality === undefined ? 1 : _ref2$quality;
|
||||||
|
|
||||||
|
var multiplier = scale;
|
||||||
|
|
||||||
|
var format = void 0;
|
||||||
|
if (mimeType == 'image/jpeg') {
|
||||||
|
format = 'jpeg';
|
||||||
|
} else if (mimeType == 'image/png') {
|
||||||
|
format = 'png';
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
fabric.loadSVGFromString(svgXml, function (objects, options) {
|
||||||
|
// If there's something wrong with the SVG, Fabric may return an empty array of objects. Graphviz appears to give us at least one <g> element back even given an empty graph, so we will assume an error in this case.
|
||||||
|
if (objects.length == 0) {
|
||||||
|
reject(new Error('Error loading SVG with Fabric'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.createElement("canvas");
|
||||||
|
element.width = options.width;
|
||||||
|
element.height = options.height;
|
||||||
|
|
||||||
|
var canvas = new fabric.Canvas(element, { enableRetinaScaling: false });
|
||||||
|
var obj = fabric.util.groupSVGElements(objects, options);
|
||||||
|
canvas.add(obj).renderAll();
|
||||||
|
|
||||||
|
var image = new Image();
|
||||||
|
image.src = canvas.toDataURL({ format: format, multiplier: multiplier, quality: quality });
|
||||||
|
image.width = options.width;
|
||||||
|
image.height = options.height;
|
||||||
|
|
||||||
|
resolve(image);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var Viz = function () {
|
||||||
|
function Viz() {
|
||||||
|
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
||||||
|
workerURL = _ref3.workerURL,
|
||||||
|
worker = _ref3.worker,
|
||||||
|
Module = _ref3.Module,
|
||||||
|
render = _ref3.render;
|
||||||
|
|
||||||
|
classCallCheck(this, Viz);
|
||||||
|
|
||||||
|
if (typeof workerURL !== 'undefined') {
|
||||||
|
this.wrapper = new WorkerWrapper(new Worker(workerURL));
|
||||||
|
} else if (typeof worker !== 'undefined') {
|
||||||
|
this.wrapper = new WorkerWrapper(worker);
|
||||||
|
} else if (typeof Module !== 'undefined' && typeof render !== 'undefined') {
|
||||||
|
this.wrapper = new ModuleWrapper(Module, render);
|
||||||
|
} else if (typeof Viz.Module !== 'undefined' && typeof Viz.render !== 'undefined') {
|
||||||
|
this.wrapper = new ModuleWrapper(Viz.Module, Viz.render);
|
||||||
|
} else {
|
||||||
|
throw new Error('Must specify workerURL or worker option, Module and render options, or include one of full.render.js or lite.render.js after viz.js.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createClass(Viz, [{
|
||||||
|
key: 'renderString',
|
||||||
|
value: function renderString(src) {
|
||||||
|
var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
||||||
|
_ref4$format = _ref4.format,
|
||||||
|
format = _ref4$format === undefined ? 'svg' : _ref4$format,
|
||||||
|
_ref4$engine = _ref4.engine,
|
||||||
|
engine = _ref4$engine === undefined ? 'dot' : _ref4$engine,
|
||||||
|
_ref4$files = _ref4.files,
|
||||||
|
files = _ref4$files === undefined ? [] : _ref4$files,
|
||||||
|
_ref4$images = _ref4.images,
|
||||||
|
images = _ref4$images === undefined ? [] : _ref4$images,
|
||||||
|
_ref4$yInvert = _ref4.yInvert,
|
||||||
|
yInvert = _ref4$yInvert === undefined ? false : _ref4$yInvert;
|
||||||
|
|
||||||
|
for (var i = 0; i < images.length; i++) {
|
||||||
|
files.push({
|
||||||
|
path: images[i].path,
|
||||||
|
data: '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n<svg width="' + images[i].width + '" height="' + images[i].height + '"></svg>'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.wrapper.render(src, { format: format, engine: engine, files: files, images: images, yInvert: yInvert });
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'renderSVGElement',
|
||||||
|
value: function renderSVGElement(src) {
|
||||||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
|
||||||
|
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
|
||||||
|
var parser = new DOMParser();
|
||||||
|
return parser.parseFromString(str, 'image/svg+xml').documentElement;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'renderImageElement',
|
||||||
|
value: function renderImageElement(src) {
|
||||||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
var scale = options.scale,
|
||||||
|
mimeType = options.mimeType,
|
||||||
|
quality = options.quality;
|
||||||
|
|
||||||
|
|
||||||
|
return this.renderString(src, _extends({}, options, { format: 'svg' })).then(function (str) {
|
||||||
|
if ((typeof fabric === 'undefined' ? 'undefined' : _typeof(fabric)) === "object" && fabric.loadSVGFromString) {
|
||||||
|
return svgXmlToImageElementFabric(str, { scale: scale, mimeType: mimeType, quality: quality });
|
||||||
|
} else {
|
||||||
|
return svgXmlToImageElement(str, { scale: scale, mimeType: mimeType, quality: quality });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
key: 'renderJSONObject',
|
||||||
|
value: function renderJSONObject(src) {
|
||||||
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
|
var format = options.format;
|
||||||
|
|
||||||
|
|
||||||
|
if (format !== 'json' || format !== 'json0') {
|
||||||
|
format = 'json';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.renderString(src, _extends({}, options, { format: format })).then(function (str) {
|
||||||
|
return JSON.parse(str);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return Viz;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return Viz;
|
||||||
|
|
||||||
|
})));
|
|
@ -0,0 +1,32 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
repeated Switch switch = 1;
|
||||||
|
repeated Machine machine = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
message Switch {
|
||||||
|
string name = 1;
|
||||||
|
string control_address = 2;
|
||||||
|
message SwitchPort {
|
||||||
|
string name = 1;
|
||||||
|
};
|
||||||
|
repeated SwitchPort managed_port = 3;
|
||||||
|
message Segment {
|
||||||
|
enum Type {
|
||||||
|
TYPE_INVALID = 0;
|
||||||
|
TYPE_VLAN = 1;
|
||||||
|
}
|
||||||
|
Type segment_type = 1;
|
||||||
|
int32 vlan_id = 2;
|
||||||
|
};
|
||||||
|
repeated Segment available_segment = 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
message Machine {
|
||||||
|
string name = 1;
|
||||||
|
message Port {
|
||||||
|
string name = 1;
|
||||||
|
};
|
||||||
|
repeated Port managed_port = 2;
|
||||||
|
};
|
|
@ -0,0 +1,64 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
package connector;
|
||||||
|
|
||||||
|
message GetPortsRequest {
|
||||||
|
};
|
||||||
|
|
||||||
|
message SwitchPort {
|
||||||
|
enum Speed {
|
||||||
|
SPEED_INVALID = 0;
|
||||||
|
SPEED_100M = 1;
|
||||||
|
SPEED_1G = 2;
|
||||||
|
SPEED_10G = 3;
|
||||||
|
};
|
||||||
|
string name = 1;
|
||||||
|
Speed speed = 2;
|
||||||
|
enum LinkState {
|
||||||
|
LINKSTATE_INVALID = 0;
|
||||||
|
LINKSTATE_DOWN = 1;
|
||||||
|
LINKSTATE_UP = 2;
|
||||||
|
};
|
||||||
|
LinkState link_state = 3;
|
||||||
|
enum PortMode {
|
||||||
|
PORTMODE_INVALID = 0;
|
||||||
|
// Interface is bridged to a VLAN, untagged.
|
||||||
|
PORTMODE_SWITCHPORT_UNTAGGED = 1;
|
||||||
|
// Interfaces is bridged to several tagged 802.1q VLANs.
|
||||||
|
PORTMODE_SWITCHPORT_TAGGED = 2;
|
||||||
|
// Interface is in 'generic', both tagged 802.1q and untagged mode.
|
||||||
|
PORTMODE_SWITCHPORT_GENERIC = 3;
|
||||||
|
// Interface is routed, ie routes packets from a separate L3 network
|
||||||
|
// and the Switch is the default gateway for machines in this network.
|
||||||
|
PORTMODE_ROUTED = 4;
|
||||||
|
// Interface is in a configuration state that cannot be clearly stated
|
||||||
|
// in terms of this enum, and should be reconfigured.
|
||||||
|
PORTMODE_MANGLED = 5;
|
||||||
|
};
|
||||||
|
PortMode port_mode = 4;
|
||||||
|
// For PORTMODE_SWITCHPORT_UNTAGGED and PORTMODE_SWITCHPORT_GENERIC, the
|
||||||
|
// VLAN ID that this interface is natively bridged to.
|
||||||
|
int32 vlan_native = 5;
|
||||||
|
// For PORTMODE_SWITCHPORT_TAGGED and PORTMODE_SWITCHPORT_GENERIC, the VLAN
|
||||||
|
// IDs that the interface is bridged to using 802.1q tags.
|
||||||
|
repeated int32 vlan_tagged = 6;
|
||||||
|
// For PORTMODE_ROUTED
|
||||||
|
repeated string prefixes = 7;
|
||||||
|
// Interface MTU
|
||||||
|
int32 mtu = 8;
|
||||||
|
enum SpanningTreeMode {
|
||||||
|
SPANNING_TREE_MODE_INVALID = 0;
|
||||||
|
// Send STP BPDU, on timeout switch to Forwarding.
|
||||||
|
SPANNING_TREE_MODE_AUTO_PORTFAST = 1;
|
||||||
|
// Switch to Forwarding immediately on link up.
|
||||||
|
SPANNING_TREE_MODE_PORTFAST = 2;
|
||||||
|
};
|
||||||
|
SpanningTreeMode spanning_tree_mode = 9;
|
||||||
|
};
|
||||||
|
|
||||||
|
message GetPortsResponse {
|
||||||
|
repeated SwitchPort ports = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
service SwitchControl {
|
||||||
|
rpc GetPorts(GetPortsRequest) returns (GetPortsResponse);
|
||||||
|
};
|
|
@ -0,0 +1,213 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/digitalocean/go-netbox/netbox/client"
|
||||||
|
"github.com/digitalocean/go-netbox/netbox/client/dcim"
|
||||||
|
"github.com/digitalocean/go-netbox/netbox/models"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
confpb "code.hackerspace.pl/q3k/topo/proto/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MachinePort struct {
|
||||||
|
Machine *Machine
|
||||||
|
OtherEnd *SwitchPort
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchPort struct {
|
||||||
|
Switch *Switch
|
||||||
|
OtherEnd *MachinePort
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Machine struct {
|
||||||
|
Name string
|
||||||
|
Complete bool
|
||||||
|
|
||||||
|
Ports map[string]*MachinePort
|
||||||
|
}
|
||||||
|
|
||||||
|
type Switch struct {
|
||||||
|
Name string
|
||||||
|
Complete bool
|
||||||
|
|
||||||
|
Ports map[string]*SwitchPort
|
||||||
|
}
|
||||||
|
|
||||||
|
type Graph struct {
|
||||||
|
Switches map[string]*Switch
|
||||||
|
Machines map[string]*Machine
|
||||||
|
Mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Graph {
|
||||||
|
return &Graph{
|
||||||
|
Switches: make(map[string]*Switch),
|
||||||
|
Machines: make(map[string]*Machine),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) RemoveMachine(name string) {
|
||||||
|
glog.Infof("Removed machine %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) RemoveSwitch(name string) {
|
||||||
|
glog.Infof("Removed switch %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) LoadConfig(conf *confpb.Config) error {
|
||||||
|
loadedMachines := make(map[string]bool)
|
||||||
|
loadedSwitches := make(map[string]bool)
|
||||||
|
|
||||||
|
// Add new machines and switches.
|
||||||
|
for _, machinepb := range conf.Machine {
|
||||||
|
if machinepb.Name == "" {
|
||||||
|
return fmt.Errorf("empty machine name")
|
||||||
|
}
|
||||||
|
if loadedMachines[machinepb.Name] {
|
||||||
|
return fmt.Errorf("duplicate machine name: %v", machinepb.Name)
|
||||||
|
}
|
||||||
|
machine, ok := g.Machines[machinepb.Name]
|
||||||
|
if !ok {
|
||||||
|
machine = &Machine{
|
||||||
|
Name: machinepb.Name,
|
||||||
|
Ports: make(map[string]*MachinePort),
|
||||||
|
}
|
||||||
|
for _, portpb := range machinepb.ManagedPort {
|
||||||
|
machine.Ports[portpb.Name] = &MachinePort{
|
||||||
|
Name: portpb.Name,
|
||||||
|
Machine: machine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Machines[machinepb.Name] = machine
|
||||||
|
glog.Infof("Added machine %q with %d managed ports", machine.Name, len(machine.Ports))
|
||||||
|
}
|
||||||
|
machine.Complete = false
|
||||||
|
loadedMachines[machinepb.Name] = true
|
||||||
|
}
|
||||||
|
for _, switchpb := range conf.Switch {
|
||||||
|
if switchpb.Name == "" {
|
||||||
|
return fmt.Errorf("empty switch name")
|
||||||
|
}
|
||||||
|
if loadedSwitches[switchpb.Name] {
|
||||||
|
return fmt.Errorf("duplicate switch name: %v", switchpb.Name)
|
||||||
|
}
|
||||||
|
if loadedMachines[switchpb.Name] {
|
||||||
|
return fmt.Errorf("switch name collides with machine name: %v", switchpb.Name)
|
||||||
|
}
|
||||||
|
sw, ok := g.Switches[switchpb.Name]
|
||||||
|
if !ok {
|
||||||
|
sw = &Switch{
|
||||||
|
Name: switchpb.Name,
|
||||||
|
Ports: make(map[string]*SwitchPort),
|
||||||
|
}
|
||||||
|
for _, portpb := range switchpb.ManagedPort {
|
||||||
|
sw.Ports[portpb.Name] = &SwitchPort{
|
||||||
|
Name: portpb.Name,
|
||||||
|
Switch: sw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Switches[switchpb.Name] = sw
|
||||||
|
glog.Infof("Added switch %q with %d managed ports", sw.Name, len(sw.Ports))
|
||||||
|
}
|
||||||
|
sw.Complete = false
|
||||||
|
loadedSwitches[switchpb.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old machines and switches.
|
||||||
|
removeMachines := make(map[string]bool)
|
||||||
|
removeSwitches := make(map[string]bool)
|
||||||
|
for name, _ := range g.Switches {
|
||||||
|
if !loadedSwitches[name] {
|
||||||
|
removeSwitches[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, _ := range g.Machines {
|
||||||
|
if !loadedMachines[name] {
|
||||||
|
removeMachines[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, _ := range removeMachines {
|
||||||
|
g.RemoveMachine(name)
|
||||||
|
}
|
||||||
|
for name, _ := range removeSwitches {
|
||||||
|
g.RemoveSwitch(name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Graph) FeedFromNetbox(ctx context.Context, nb *client.NetBox) error {
|
||||||
|
// Clear all connections first, because it's easier that way.
|
||||||
|
for _, machine := range g.Machines {
|
||||||
|
for _, port := range machine.Ports {
|
||||||
|
port.OtherEnd = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, sw := range g.Switches {
|
||||||
|
for _, port := range sw.Ports {
|
||||||
|
port.OtherEnd = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load new connections.
|
||||||
|
// Iterating over just machines should be fine if all connections are
|
||||||
|
// guaranteed to be between machines and switches (which is the model for
|
||||||
|
// now).
|
||||||
|
for _, machine := range g.Machines {
|
||||||
|
req := &dcim.DcimInterfaceConnectionsListParams{
|
||||||
|
Device: &machine.Name,
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
res, err := nb.Dcim.DcimInterfaceConnectionsList(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("while querying information about %q: %v", machine.Name, err)
|
||||||
|
}
|
||||||
|
for _, connection := range res.Payload.Results {
|
||||||
|
ia := connection.InterfaceA
|
||||||
|
ib := connection.InterfaceB
|
||||||
|
if ia == nil || ib == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find which way this thing actually connects.
|
||||||
|
var thisSide, otherSide *models.PeerInterface
|
||||||
|
if ia.Device.Name == machine.Name {
|
||||||
|
thisSide = ia
|
||||||
|
otherSide = ib
|
||||||
|
} else if ib.Device.Name == machine.Name {
|
||||||
|
thisSide = ib
|
||||||
|
otherSide = ia
|
||||||
|
} else {
|
||||||
|
glog.Warning("Netbox connectivity for %q reported a link without it involced..?", machine.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
thisPort, ok := machine.Ports[*thisSide.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sw, ok := g.Switches[otherSide.Device.Name]
|
||||||
|
if !ok {
|
||||||
|
glog.Warningf("Machine %q port %q is managed but connected to unknown device %q", machine.Name, thisPort.Name, otherSide.Device.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
otherPort, ok := sw.Ports[*otherSide.Name]
|
||||||
|
if !ok {
|
||||||
|
glog.Warningf("Machine %q port %q is managed but connected to unmanaged port %q on %q", machine.Name, thisPort.Name, otherSide.Name, sw.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect the two together!
|
||||||
|
thisPort.OtherEnd = otherPort
|
||||||
|
otherPort.OtherEnd = thisPort
|
||||||
|
glog.Infof("Connected: %s/%s <-> %s/%s", machine.Name, thisPort.Name, sw.Name, otherPort.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
//go:generate packr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/mirko"
|
||||||
|
"github.com/digitalocean/go-netbox/netbox"
|
||||||
|
"github.com/digitalocean/go-netbox/netbox/client"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
confpb "code.hackerspace.pl/q3k/topo/proto/config"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/topo/graph"
|
||||||
|
"code.hackerspace.pl/q3k/topo/state"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagConfigPath string
|
||||||
|
flagNetboxHost string
|
||||||
|
flagNetboxAPIKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Set("logtostderr", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&flagConfigPath, "config_path", "./topo.pb.text", "Text proto configuration of Topo (per config.proto)")
|
||||||
|
flag.StringVar(&flagNetboxHost, "netbox_host", "netbox.bgp.wtf", "Netbox host")
|
||||||
|
flag.StringVar(&flagNetboxAPIKey, "netbox_api_key", "", "Netbox API key")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
m := mirko.New()
|
||||||
|
if err := m.Listen(); err != nil {
|
||||||
|
glog.Exitf("Listen(): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(flagConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
glog.Exitf("Could not read config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := confpb.Config{}
|
||||||
|
proto.UnmarshalText(string(data), &config)
|
||||||
|
|
||||||
|
stm := state.NewManager()
|
||||||
|
err = stm.FetchState(ctx, &config)
|
||||||
|
if err != nil {
|
||||||
|
glog.Exitf("Initial state fetch failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gr := graph.New()
|
||||||
|
err = gr.LoadConfig(&config)
|
||||||
|
if err != nil {
|
||||||
|
glog.Exitf("Initial config load failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.DefaultSchemes = []string{"https"}
|
||||||
|
nb := netbox.NewNetboxWithAPIKey(flagNetboxHost, flagNetboxAPIKey)
|
||||||
|
err = gr.FeedFromNetbox(ctx, nb)
|
||||||
|
if err != nil {
|
||||||
|
glog.Exitf("Initial netbox feed failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := NewService(gr, stm)
|
||||||
|
s.Setup(m)
|
||||||
|
|
||||||
|
if err := m.Serve(); err != nil {
|
||||||
|
glog.Exitf("Serve(): %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
|
@ -0,0 +1,365 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: config.proto
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
math "math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type Switch_Segment_Type int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Switch_Segment_TYPE_INVALID Switch_Segment_Type = 0
|
||||||
|
Switch_Segment_TYPE_VLAN Switch_Segment_Type = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var Switch_Segment_Type_name = map[int32]string{
|
||||||
|
0: "TYPE_INVALID",
|
||||||
|
1: "TYPE_VLAN",
|
||||||
|
}
|
||||||
|
|
||||||
|
var Switch_Segment_Type_value = map[string]int32{
|
||||||
|
"TYPE_INVALID": 0,
|
||||||
|
"TYPE_VLAN": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Switch_Segment_Type) String() string {
|
||||||
|
return proto.EnumName(Switch_Segment_Type_name, int32(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Switch_Segment_Type) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{1, 1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Switch []*Switch `protobuf:"bytes,1,rep,name=switch,proto3" json:"switch,omitempty"`
|
||||||
|
Machine []*Machine `protobuf:"bytes,2,rep,name=machine,proto3" json:"machine,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Config) Reset() { *m = Config{} }
|
||||||
|
func (m *Config) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Config) ProtoMessage() {}
|
||||||
|
func (*Config) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Config) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Config.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Config) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Config.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Config) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Config.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Config) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Config.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Config) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Config.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Config proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Config) GetSwitch() []*Switch {
|
||||||
|
if m != nil {
|
||||||
|
return m.Switch
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Config) GetMachine() []*Machine {
|
||||||
|
if m != nil {
|
||||||
|
return m.Machine
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Switch struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
ControlAddress string `protobuf:"bytes,2,opt,name=control_address,json=controlAddress,proto3" json:"control_address,omitempty"`
|
||||||
|
ManagedPort []*Switch_SwitchPort `protobuf:"bytes,3,rep,name=managed_port,json=managedPort,proto3" json:"managed_port,omitempty"`
|
||||||
|
AvailableSegment []*Switch_Segment `protobuf:"bytes,4,rep,name=available_segment,json=availableSegment,proto3" json:"available_segment,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch) Reset() { *m = Switch{} }
|
||||||
|
func (m *Switch) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Switch) ProtoMessage() {}
|
||||||
|
func (*Switch) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Switch.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Switch) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Switch.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Switch) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Switch.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Switch) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Switch.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Switch) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Switch.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Switch proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Switch) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch) GetControlAddress() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.ControlAddress
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch) GetManagedPort() []*Switch_SwitchPort {
|
||||||
|
if m != nil {
|
||||||
|
return m.ManagedPort
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch) GetAvailableSegment() []*Switch_Segment {
|
||||||
|
if m != nil {
|
||||||
|
return m.AvailableSegment
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Switch_SwitchPort struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch_SwitchPort) Reset() { *m = Switch_SwitchPort{} }
|
||||||
|
func (m *Switch_SwitchPort) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Switch_SwitchPort) ProtoMessage() {}
|
||||||
|
func (*Switch_SwitchPort) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch_SwitchPort) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Switch_SwitchPort.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Switch_SwitchPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Switch_SwitchPort.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Switch_SwitchPort) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Switch_SwitchPort.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Switch_SwitchPort) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Switch_SwitchPort.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Switch_SwitchPort) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Switch_SwitchPort.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Switch_SwitchPort proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Switch_SwitchPort) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Switch_Segment struct {
|
||||||
|
SegmentType Switch_Segment_Type `protobuf:"varint,1,opt,name=segment_type,json=segmentType,proto3,enum=Switch_Segment_Type" json:"segment_type,omitempty"`
|
||||||
|
VlanId int32 `protobuf:"varint,2,opt,name=vlan_id,json=vlanId,proto3" json:"vlan_id,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch_Segment) Reset() { *m = Switch_Segment{} }
|
||||||
|
func (m *Switch_Segment) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Switch_Segment) ProtoMessage() {}
|
||||||
|
func (*Switch_Segment) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{1, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch_Segment) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Switch_Segment.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Switch_Segment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Switch_Segment.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Switch_Segment) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Switch_Segment.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Switch_Segment) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Switch_Segment.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Switch_Segment) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Switch_Segment.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Switch_Segment proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Switch_Segment) GetSegmentType() Switch_Segment_Type {
|
||||||
|
if m != nil {
|
||||||
|
return m.SegmentType
|
||||||
|
}
|
||||||
|
return Switch_Segment_TYPE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Switch_Segment) GetVlanId() int32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.VlanId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Machine struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
ManagedPort []*Machine_Port `protobuf:"bytes,2,rep,name=managed_port,json=managedPort,proto3" json:"managed_port,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) Reset() { *m = Machine{} }
|
||||||
|
func (m *Machine) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Machine) ProtoMessage() {}
|
||||||
|
func (*Machine) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Machine.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Machine) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Machine.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Machine) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Machine.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Machine) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Machine.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Machine) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Machine.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Machine proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Machine) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine) GetManagedPort() []*Machine_Port {
|
||||||
|
if m != nil {
|
||||||
|
return m.ManagedPort
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Machine_Port struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine_Port) Reset() { *m = Machine_Port{} }
|
||||||
|
func (m *Machine_Port) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Machine_Port) ProtoMessage() {}
|
||||||
|
func (*Machine_Port) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_3eaf2c85e69e9ea4, []int{2, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Machine_Port) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Machine_Port.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Machine_Port) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Machine_Port.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Machine_Port) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Machine_Port.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Machine_Port) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Machine_Port.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Machine_Port) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Machine_Port.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Machine_Port proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Machine_Port) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterEnum("Switch_Segment_Type", Switch_Segment_Type_name, Switch_Segment_Type_value)
|
||||||
|
proto.RegisterType((*Config)(nil), "Config")
|
||||||
|
proto.RegisterType((*Switch)(nil), "Switch")
|
||||||
|
proto.RegisterType((*Switch_SwitchPort)(nil), "Switch.SwitchPort")
|
||||||
|
proto.RegisterType((*Switch_Segment)(nil), "Switch.Segment")
|
||||||
|
proto.RegisterType((*Machine)(nil), "Machine")
|
||||||
|
proto.RegisterType((*Machine_Port)(nil), "Machine.Port")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("config.proto", fileDescriptor_3eaf2c85e69e9ea4) }
|
||||||
|
|
||||||
|
var fileDescriptor_3eaf2c85e69e9ea4 = []byte{
|
||||||
|
// 335 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x41, 0x4f, 0xc2, 0x30,
|
||||||
|
0x14, 0x76, 0x30, 0x37, 0x79, 0x0c, 0x98, 0x8d, 0x89, 0xcb, 0x2e, 0x92, 0x5d, 0xe0, 0xb4, 0x18,
|
||||||
|
0x8c, 0xf1, 0xe2, 0x85, 0xa8, 0x07, 0x12, 0x20, 0xa4, 0x12, 0x12, 0x4f, 0x4b, 0xd9, 0x2a, 0x2c,
|
||||||
|
0x6e, 0xed, 0xb2, 0x35, 0x18, 0xee, 0xfe, 0x53, 0xff, 0x88, 0xa1, 0x2d, 0x98, 0x20, 0x9e, 0xda,
|
||||||
|
0xef, 0x7b, 0xdf, 0xeb, 0x7b, 0xef, 0x7b, 0x05, 0x27, 0xe6, 0xec, 0x3d, 0x5d, 0x85, 0x45, 0xc9,
|
||||||
|
0x05, 0x0f, 0x26, 0x60, 0x3d, 0x49, 0x8c, 0x6e, 0xc0, 0xaa, 0x3e, 0x53, 0x11, 0xaf, 0x3d, 0xa3,
|
||||||
|
0x5b, 0xef, 0x37, 0x07, 0x76, 0xf8, 0x2a, 0x21, 0xd6, 0x34, 0x0a, 0xc0, 0xce, 0x49, 0xbc, 0x4e,
|
||||||
|
0x19, 0xf5, 0x6a, 0x52, 0x71, 0x11, 0x4e, 0x14, 0xc6, 0xfb, 0x40, 0xf0, 0x5d, 0x03, 0x4b, 0xa5,
|
||||||
|
0x21, 0x04, 0x26, 0x23, 0x39, 0xf5, 0x8c, 0xae, 0xd1, 0x6f, 0x60, 0x79, 0x47, 0x3d, 0xe8, 0xc4,
|
||||||
|
0x9c, 0x89, 0x92, 0x67, 0x11, 0x49, 0x92, 0x92, 0x56, 0x95, 0x57, 0x93, 0xe1, 0xb6, 0xa6, 0x87,
|
||||||
|
0x8a, 0x45, 0xf7, 0xe0, 0xe4, 0x84, 0x91, 0x15, 0x4d, 0xa2, 0x82, 0x97, 0xc2, 0xab, 0xcb, 0x82,
|
||||||
|
0x48, 0xb7, 0xa4, 0x8f, 0x19, 0x2f, 0x05, 0x6e, 0x6a, 0xdd, 0x0e, 0xa0, 0x47, 0xb8, 0x24, 0x1b,
|
||||||
|
0x92, 0x66, 0x64, 0x99, 0xd1, 0xa8, 0xa2, 0xab, 0x9c, 0x32, 0xe1, 0x99, 0x32, 0xb7, 0x73, 0xc8,
|
||||||
|
0x55, 0x34, 0x76, 0x0f, 0x4a, 0xcd, 0xf8, 0x5d, 0x80, 0xdf, 0x87, 0x4f, 0xf5, 0xef, 0x7f, 0x19,
|
||||||
|
0x60, 0x6b, 0x35, 0x7a, 0x00, 0x47, 0x57, 0x88, 0xc4, 0xb6, 0x50, 0xba, 0xf6, 0xe0, 0xea, 0xa8,
|
||||||
|
0x4c, 0x38, 0xdf, 0x16, 0x14, 0x37, 0xb5, 0x72, 0x07, 0xd0, 0x35, 0xd8, 0x9b, 0x8c, 0xb0, 0x28,
|
||||||
|
0x4d, 0xe4, 0xf0, 0xe7, 0xd8, 0xda, 0xc1, 0x51, 0x12, 0xf4, 0xc0, 0x94, 0x02, 0x17, 0x9c, 0xf9,
|
||||||
|
0xdb, 0xec, 0x25, 0x1a, 0x4d, 0x17, 0xc3, 0xf1, 0xe8, 0xd9, 0x3d, 0x43, 0x2d, 0x68, 0x48, 0x66,
|
||||||
|
0x31, 0x1e, 0x4e, 0x5d, 0x23, 0xf8, 0x00, 0x5b, 0x3b, 0x7f, 0xd2, 0xe5, 0xdb, 0x23, 0xf3, 0xd4,
|
||||||
|
0xb6, 0x5a, 0xfb, 0x6d, 0x85, 0x7f, 0x7c, 0xf3, 0x7d, 0x30, 0xff, 0x9b, 0x79, 0x69, 0xc9, 0x8f,
|
||||||
|
0x72, 0xf7, 0x13, 0x00, 0x00, 0xff, 0xff, 0x58, 0xe3, 0x1a, 0xb1, 0x38, 0x02, 0x00, 0x00,
|
||||||
|
}
|
|
@ -0,0 +1,459 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: control.proto
|
||||||
|
|
||||||
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
math "math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type SwitchPort_Speed int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
SwitchPort_SPEED_INVALID SwitchPort_Speed = 0
|
||||||
|
SwitchPort_SPEED_100M SwitchPort_Speed = 1
|
||||||
|
SwitchPort_SPEED_1G SwitchPort_Speed = 2
|
||||||
|
SwitchPort_SPEED_10G SwitchPort_Speed = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var SwitchPort_Speed_name = map[int32]string{
|
||||||
|
0: "SPEED_INVALID",
|
||||||
|
1: "SPEED_100M",
|
||||||
|
2: "SPEED_1G",
|
||||||
|
3: "SPEED_10G",
|
||||||
|
}
|
||||||
|
|
||||||
|
var SwitchPort_Speed_value = map[string]int32{
|
||||||
|
"SPEED_INVALID": 0,
|
||||||
|
"SPEED_100M": 1,
|
||||||
|
"SPEED_1G": 2,
|
||||||
|
"SPEED_10G": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x SwitchPort_Speed) String() string {
|
||||||
|
return proto.EnumName(SwitchPort_Speed_name, int32(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SwitchPort_Speed) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchPort_LinkState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
SwitchPort_LINKSTATE_INVALID SwitchPort_LinkState = 0
|
||||||
|
SwitchPort_LINKSTATE_DOWN SwitchPort_LinkState = 1
|
||||||
|
SwitchPort_LINKSTATE_UP SwitchPort_LinkState = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var SwitchPort_LinkState_name = map[int32]string{
|
||||||
|
0: "LINKSTATE_INVALID",
|
||||||
|
1: "LINKSTATE_DOWN",
|
||||||
|
2: "LINKSTATE_UP",
|
||||||
|
}
|
||||||
|
|
||||||
|
var SwitchPort_LinkState_value = map[string]int32{
|
||||||
|
"LINKSTATE_INVALID": 0,
|
||||||
|
"LINKSTATE_DOWN": 1,
|
||||||
|
"LINKSTATE_UP": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x SwitchPort_LinkState) String() string {
|
||||||
|
return proto.EnumName(SwitchPort_LinkState_name, int32(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SwitchPort_LinkState) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{1, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchPort_PortMode int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
SwitchPort_PORTMODE_INVALID SwitchPort_PortMode = 0
|
||||||
|
// Interface is bridged to a VLAN, untagged.
|
||||||
|
SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED SwitchPort_PortMode = 1
|
||||||
|
// Interfaces is bridged to several tagged 802.1q VLANs.
|
||||||
|
SwitchPort_PORTMODE_SWITCHPORT_TAGGED SwitchPort_PortMode = 2
|
||||||
|
// Interface is in 'generic', both tagged 802.1q and untagged mode.
|
||||||
|
SwitchPort_PORTMODE_SWITCHPORT_GENERIC SwitchPort_PortMode = 3
|
||||||
|
// Interface is routed, ie routes packets from a separate L3 network
|
||||||
|
// and the Switch is the default gateway for machines in this network.
|
||||||
|
SwitchPort_PORTMODE_ROUTED SwitchPort_PortMode = 4
|
||||||
|
// Interface is in a configuration state that cannot be clearly stated
|
||||||
|
// in terms of this enum, and should be reconfigured.
|
||||||
|
SwitchPort_PORTMODE_MANGLED SwitchPort_PortMode = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
var SwitchPort_PortMode_name = map[int32]string{
|
||||||
|
0: "PORTMODE_INVALID",
|
||||||
|
1: "PORTMODE_SWITCHPORT_UNTAGGED",
|
||||||
|
2: "PORTMODE_SWITCHPORT_TAGGED",
|
||||||
|
3: "PORTMODE_SWITCHPORT_GENERIC",
|
||||||
|
4: "PORTMODE_ROUTED",
|
||||||
|
5: "PORTMODE_MANGLED",
|
||||||
|
}
|
||||||
|
|
||||||
|
var SwitchPort_PortMode_value = map[string]int32{
|
||||||
|
"PORTMODE_INVALID": 0,
|
||||||
|
"PORTMODE_SWITCHPORT_UNTAGGED": 1,
|
||||||
|
"PORTMODE_SWITCHPORT_TAGGED": 2,
|
||||||
|
"PORTMODE_SWITCHPORT_GENERIC": 3,
|
||||||
|
"PORTMODE_ROUTED": 4,
|
||||||
|
"PORTMODE_MANGLED": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x SwitchPort_PortMode) String() string {
|
||||||
|
return proto.EnumName(SwitchPort_PortMode_name, int32(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SwitchPort_PortMode) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{1, 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchPort_SpanningTreeMode int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
SwitchPort_SPANNING_TREE_MODE_INVALID SwitchPort_SpanningTreeMode = 0
|
||||||
|
// Send STP BPDU, on timeout switch to Forwarding.
|
||||||
|
SwitchPort_SPANNING_TREE_MODE_AUTO_PORTFAST SwitchPort_SpanningTreeMode = 1
|
||||||
|
// Switch to Forwarding immediately on link up.
|
||||||
|
SwitchPort_SPANNING_TREE_MODE_PORTFAST SwitchPort_SpanningTreeMode = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var SwitchPort_SpanningTreeMode_name = map[int32]string{
|
||||||
|
0: "SPANNING_TREE_MODE_INVALID",
|
||||||
|
1: "SPANNING_TREE_MODE_AUTO_PORTFAST",
|
||||||
|
2: "SPANNING_TREE_MODE_PORTFAST",
|
||||||
|
}
|
||||||
|
|
||||||
|
var SwitchPort_SpanningTreeMode_value = map[string]int32{
|
||||||
|
"SPANNING_TREE_MODE_INVALID": 0,
|
||||||
|
"SPANNING_TREE_MODE_AUTO_PORTFAST": 1,
|
||||||
|
"SPANNING_TREE_MODE_PORTFAST": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x SwitchPort_SpanningTreeMode) String() string {
|
||||||
|
return proto.EnumName(SwitchPort_SpanningTreeMode_name, int32(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SwitchPort_SpanningTreeMode) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{1, 3}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPortsRequest struct {
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPortsRequest) Reset() { *m = GetPortsRequest{} }
|
||||||
|
func (m *GetPortsRequest) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetPortsRequest) ProtoMessage() {}
|
||||||
|
func (*GetPortsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPortsRequest) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_GetPortsRequest.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *GetPortsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_GetPortsRequest.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *GetPortsRequest) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_GetPortsRequest.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *GetPortsRequest) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_GetPortsRequest.Size(m)
|
||||||
|
}
|
||||||
|
func (m *GetPortsRequest) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_GetPortsRequest.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_GetPortsRequest proto.InternalMessageInfo
|
||||||
|
|
||||||
|
type SwitchPort struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Speed SwitchPort_Speed `protobuf:"varint,2,opt,name=speed,proto3,enum=connector.SwitchPort_Speed" json:"speed,omitempty"`
|
||||||
|
LinkState SwitchPort_LinkState `protobuf:"varint,3,opt,name=link_state,json=linkState,proto3,enum=connector.SwitchPort_LinkState" json:"link_state,omitempty"`
|
||||||
|
PortMode SwitchPort_PortMode `protobuf:"varint,4,opt,name=port_mode,json=portMode,proto3,enum=connector.SwitchPort_PortMode" json:"port_mode,omitempty"`
|
||||||
|
// For PORTMODE_SWITCHPORT_UNTAGGED and PORTMODE_SWITCHPORT_GENERIC, the
|
||||||
|
// VLAN ID that this interface is natively bridged to.
|
||||||
|
VlanNative int32 `protobuf:"varint,5,opt,name=vlan_native,json=vlanNative,proto3" json:"vlan_native,omitempty"`
|
||||||
|
// For PORTMODE_SWITCHPORT_TAGGED and PORTMODE_SWITCHPORT_GENERIC, the VLAN
|
||||||
|
// IDs that the interface is bridged to using 802.1q tags.
|
||||||
|
VlanTagged []int32 `protobuf:"varint,6,rep,packed,name=vlan_tagged,json=vlanTagged,proto3" json:"vlan_tagged,omitempty"`
|
||||||
|
// For PORTMODE_ROUTED
|
||||||
|
Prefixes []string `protobuf:"bytes,7,rep,name=prefixes,proto3" json:"prefixes,omitempty"`
|
||||||
|
// Interface MTU
|
||||||
|
Mtu int32 `protobuf:"varint,8,opt,name=mtu,proto3" json:"mtu,omitempty"`
|
||||||
|
SpanningTreeMode SwitchPort_SpanningTreeMode `protobuf:"varint,9,opt,name=spanning_tree_mode,json=spanningTreeMode,proto3,enum=connector.SwitchPort_SpanningTreeMode" json:"spanning_tree_mode,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) Reset() { *m = SwitchPort{} }
|
||||||
|
func (m *SwitchPort) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SwitchPort) ProtoMessage() {}
|
||||||
|
func (*SwitchPort) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_SwitchPort.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *SwitchPort) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_SwitchPort.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *SwitchPort) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_SwitchPort.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *SwitchPort) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_SwitchPort.Size(m)
|
||||||
|
}
|
||||||
|
func (m *SwitchPort) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_SwitchPort.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_SwitchPort proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetSpeed() SwitchPort_Speed {
|
||||||
|
if m != nil {
|
||||||
|
return m.Speed
|
||||||
|
}
|
||||||
|
return SwitchPort_SPEED_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetLinkState() SwitchPort_LinkState {
|
||||||
|
if m != nil {
|
||||||
|
return m.LinkState
|
||||||
|
}
|
||||||
|
return SwitchPort_LINKSTATE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetPortMode() SwitchPort_PortMode {
|
||||||
|
if m != nil {
|
||||||
|
return m.PortMode
|
||||||
|
}
|
||||||
|
return SwitchPort_PORTMODE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetVlanNative() int32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.VlanNative
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetVlanTagged() []int32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.VlanTagged
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetPrefixes() []string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Prefixes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetMtu() int32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.Mtu
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SwitchPort) GetSpanningTreeMode() SwitchPort_SpanningTreeMode {
|
||||||
|
if m != nil {
|
||||||
|
return m.SpanningTreeMode
|
||||||
|
}
|
||||||
|
return SwitchPort_SPANNING_TREE_MODE_INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPortsResponse struct {
|
||||||
|
Ports []*SwitchPort `protobuf:"bytes,1,rep,name=ports,proto3" json:"ports,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPortsResponse) Reset() { *m = GetPortsResponse{} }
|
||||||
|
func (m *GetPortsResponse) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*GetPortsResponse) ProtoMessage() {}
|
||||||
|
func (*GetPortsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_0c5120591600887d, []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GetPortsResponse) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_GetPortsResponse.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *GetPortsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_GetPortsResponse.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *GetPortsResponse) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_GetPortsResponse.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *GetPortsResponse) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_GetPortsResponse.Size(m)
|
||||||
|
}
|
||||||
|
func (m *GetPortsResponse) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_GetPortsResponse.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_GetPortsResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *GetPortsResponse) GetPorts() []*SwitchPort {
|
||||||
|
if m != nil {
|
||||||
|
return m.Ports
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterEnum("connector.SwitchPort_Speed", SwitchPort_Speed_name, SwitchPort_Speed_value)
|
||||||
|
proto.RegisterEnum("connector.SwitchPort_LinkState", SwitchPort_LinkState_name, SwitchPort_LinkState_value)
|
||||||
|
proto.RegisterEnum("connector.SwitchPort_PortMode", SwitchPort_PortMode_name, SwitchPort_PortMode_value)
|
||||||
|
proto.RegisterEnum("connector.SwitchPort_SpanningTreeMode", SwitchPort_SpanningTreeMode_name, SwitchPort_SpanningTreeMode_value)
|
||||||
|
proto.RegisterType((*GetPortsRequest)(nil), "connector.GetPortsRequest")
|
||||||
|
proto.RegisterType((*SwitchPort)(nil), "connector.SwitchPort")
|
||||||
|
proto.RegisterType((*GetPortsResponse)(nil), "connector.GetPortsResponse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("control.proto", fileDescriptor_0c5120591600887d) }
|
||||||
|
|
||||||
|
var fileDescriptor_0c5120591600887d = []byte{
|
||||||
|
// 561 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xd1, 0x6e, 0x9b, 0x4a,
|
||||||
|
0x10, 0xbd, 0x98, 0x38, 0x17, 0x26, 0x71, 0xb2, 0x99, 0x36, 0x12, 0x72, 0xaa, 0x04, 0xa1, 0xaa,
|
||||||
|
0xb2, 0x54, 0xc9, 0x4a, 0xd2, 0xc7, 0x4a, 0xad, 0x90, 0xa1, 0x04, 0xd5, 0xc6, 0xd6, 0xb2, 0x6e,
|
||||||
|
0x1e, 0x11, 0xb5, 0xb7, 0x29, 0x8a, 0xb3, 0x50, 0xd8, 0xa4, 0xed, 0x4f, 0xf5, 0x6f, 0xfa, 0x3f,
|
||||||
|
0x15, 0x10, 0xe3, 0xd4, 0xa2, 0x2f, 0x68, 0xe6, 0xcc, 0x39, 0xb3, 0x87, 0xd1, 0xce, 0x42, 0x6f,
|
||||||
|
0x91, 0x0a, 0x99, 0xa7, 0xab, 0x61, 0x96, 0xa7, 0x32, 0x45, 0x7d, 0x91, 0x0a, 0xc1, 0x17, 0x32,
|
||||||
|
0xcd, 0xad, 0x23, 0x38, 0xf4, 0xb8, 0x9c, 0xa5, 0xb9, 0x2c, 0x28, 0xff, 0x76, 0xcf, 0x0b, 0x69,
|
||||||
|
0xfd, 0xde, 0x05, 0x08, 0xbf, 0x27, 0x72, 0xf1, 0xb5, 0x84, 0x11, 0x61, 0x47, 0xc4, 0x77, 0xdc,
|
||||||
|
0x50, 0x4c, 0x65, 0xa0, 0xd3, 0x2a, 0xc6, 0x0b, 0xe8, 0x16, 0x19, 0xe7, 0x4b, 0xa3, 0x63, 0x2a,
|
||||||
|
0x83, 0x83, 0xcb, 0x93, 0x61, 0xd3, 0x70, 0xb8, 0x51, 0x0e, 0xc3, 0x92, 0x42, 0x6b, 0x26, 0xbe,
|
||||||
|
0x03, 0x58, 0x25, 0xe2, 0x36, 0x2a, 0x64, 0x2c, 0xb9, 0xa1, 0x56, 0xba, 0xb3, 0x76, 0xdd, 0x38,
|
||||||
|
0x11, 0xb7, 0x61, 0x49, 0xa3, 0xfa, 0x6a, 0x1d, 0xe2, 0x5b, 0xd0, 0xb3, 0x34, 0x97, 0xd1, 0x5d,
|
||||||
|
0xba, 0xe4, 0xc6, 0x4e, 0x25, 0x3f, 0x6d, 0x97, 0x97, 0x9f, 0x49, 0xba, 0xe4, 0x54, 0xcb, 0x1e,
|
||||||
|
0x23, 0x3c, 0x83, 0xbd, 0x87, 0x55, 0x2c, 0x22, 0x11, 0xcb, 0xe4, 0x81, 0x1b, 0x5d, 0x53, 0x19,
|
||||||
|
0x74, 0x29, 0x94, 0x50, 0x50, 0x21, 0x0d, 0x41, 0xc6, 0x37, 0x37, 0x7c, 0x69, 0xec, 0x9a, 0xea,
|
||||||
|
0x9a, 0xc0, 0x2a, 0x04, 0xfb, 0xa0, 0x65, 0x39, 0xff, 0x92, 0xfc, 0xe0, 0x85, 0xf1, 0xbf, 0xa9,
|
||||||
|
0x0e, 0x74, 0xda, 0xe4, 0x48, 0x40, 0xbd, 0x93, 0xf7, 0x86, 0x56, 0x75, 0x2d, 0x43, 0x64, 0x80,
|
||||||
|
0x45, 0x16, 0x0b, 0x91, 0x88, 0x9b, 0x48, 0xe6, 0x9c, 0xd7, 0xae, 0xf5, 0xca, 0xf5, 0xab, 0x7f,
|
||||||
|
0x0d, 0xab, 0xe6, 0xb3, 0x9c, 0xf3, 0xca, 0x3d, 0x29, 0xb6, 0x10, 0xcb, 0x83, 0x6e, 0x35, 0x52,
|
||||||
|
0x3c, 0x82, 0x5e, 0x38, 0x73, 0x5d, 0x27, 0xf2, 0x83, 0x4f, 0xf6, 0xd8, 0x77, 0xc8, 0x7f, 0x78,
|
||||||
|
0x00, 0x50, 0x43, 0x17, 0xe7, 0xe7, 0x13, 0xa2, 0xe0, 0x3e, 0x68, 0x8f, 0xb9, 0x47, 0x3a, 0xd8,
|
||||||
|
0x03, 0x7d, 0x5d, 0xf5, 0x88, 0x6a, 0x5d, 0x81, 0xde, 0xcc, 0x18, 0x8f, 0xe1, 0x68, 0xec, 0x07,
|
||||||
|
0x1f, 0x43, 0x66, 0x33, 0xf7, 0x49, 0x43, 0x84, 0x83, 0x0d, 0xec, 0x4c, 0xaf, 0x03, 0xa2, 0x20,
|
||||||
|
0x81, 0xfd, 0x0d, 0x36, 0x9f, 0x91, 0x8e, 0xf5, 0x4b, 0x01, 0x6d, 0x3d, 0x6f, 0x7c, 0x0e, 0x64,
|
||||||
|
0x36, 0xa5, 0x6c, 0x32, 0x75, 0x9e, 0x36, 0x32, 0xe1, 0x45, 0x83, 0x86, 0xd7, 0x3e, 0x1b, 0x5d,
|
||||||
|
0x95, 0x69, 0x34, 0x0f, 0x98, 0xed, 0x79, 0xae, 0x43, 0x14, 0x3c, 0x85, 0x7e, 0x1b, 0xe3, 0xb1,
|
||||||
|
0xde, 0xc1, 0x33, 0x38, 0x69, 0xab, 0x7b, 0x6e, 0xe0, 0x52, 0x7f, 0x44, 0x54, 0x7c, 0x06, 0x87,
|
||||||
|
0x0d, 0x81, 0x4e, 0xe7, 0xcc, 0x75, 0xc8, 0xce, 0x5f, 0x6e, 0x26, 0x76, 0xe0, 0x8d, 0x5d, 0x87,
|
||||||
|
0x74, 0xad, 0x9f, 0x40, 0xb6, 0x27, 0x5d, 0x9e, 0x1f, 0xce, 0xec, 0x20, 0xf0, 0x03, 0x2f, 0x62,
|
||||||
|
0xd4, 0x75, 0xa3, 0xad, 0x3f, 0x78, 0x09, 0x66, 0x4b, 0xdd, 0x9e, 0xb3, 0x69, 0x54, 0x9e, 0xf0,
|
||||||
|
0xc1, 0x0e, 0x19, 0x51, 0x4a, 0x97, 0x2d, 0xac, 0x86, 0xd0, 0xb1, 0xde, 0x03, 0xd9, 0xac, 0x5a,
|
||||||
|
0x91, 0xa5, 0xa2, 0xe0, 0xf8, 0x1a, 0xba, 0xe5, 0x25, 0x2d, 0x0c, 0xc5, 0x54, 0x07, 0x7b, 0x97,
|
||||||
|
0xc7, 0xad, 0x77, 0x83, 0xd6, 0x9c, 0x4b, 0x06, 0xbd, 0x1a, 0x1c, 0xd5, 0xdb, 0x8c, 0x23, 0xd0,
|
||||||
|
0xd6, 0x1d, 0xb1, 0xff, 0x44, 0xba, 0xb5, 0xd1, 0xfd, 0x93, 0xd6, 0x5a, 0x6d, 0xe1, 0xf3, 0x6e,
|
||||||
|
0xf5, 0x26, 0xbc, 0xf9, 0x13, 0x00, 0x00, 0xff, 0xff, 0xae, 0x71, 0xa0, 0x75, 0x24, 0x04, 0x00,
|
||||||
|
0x00,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ context.Context
|
||||||
|
var _ grpc.ClientConn
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
const _ = grpc.SupportPackageIsVersion4
|
||||||
|
|
||||||
|
// SwitchControlClient is the client API for SwitchControl service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
|
||||||
|
type SwitchControlClient interface {
|
||||||
|
GetPorts(ctx context.Context, in *GetPortsRequest, opts ...grpc.CallOption) (*GetPortsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type switchControlClient struct {
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSwitchControlClient(cc *grpc.ClientConn) SwitchControlClient {
|
||||||
|
return &switchControlClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *switchControlClient) GetPorts(ctx context.Context, in *GetPortsRequest, opts ...grpc.CallOption) (*GetPortsResponse, error) {
|
||||||
|
out := new(GetPortsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/connector.SwitchControl/GetPorts", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchControlServer is the server API for SwitchControl service.
|
||||||
|
type SwitchControlServer interface {
|
||||||
|
GetPorts(context.Context, *GetPortsRequest) (*GetPortsResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSwitchControlServer(s *grpc.Server, srv SwitchControlServer) {
|
||||||
|
s.RegisterService(&_SwitchControl_serviceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _SwitchControl_GetPorts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetPortsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(SwitchControlServer).GetPorts(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/connector.SwitchControl/GetPorts",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(SwitchControlServer).GetPorts(ctx, req.(*GetPortsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _SwitchControl_serviceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "connector.SwitchControl",
|
||||||
|
HandlerType: (*SwitchControlServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "GetPorts",
|
||||||
|
Handler: _SwitchControl_GetPorts_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "control.proto",
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
//go:generate protoc -I.. ../control.proto --go_out=plugins=grpc:control
|
||||||
|
//go:generate protoc -I.. ../config.proto --go_out=plugins=grpc:config
|
||||||
|
|
||||||
|
package proto
|
|
@ -0,0 +1,224 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/mirko"
|
||||||
|
"github.com/gobuffalo/packr"
|
||||||
|
"github.com/q3k/statusz"
|
||||||
|
"vbom.ml/util/sortorder"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/topo/graph"
|
||||||
|
"code.hackerspace.pl/q3k/topo/state"
|
||||||
|
|
||||||
|
pb "code.hackerspace.pl/q3k/topo/proto/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
gr *graph.Graph
|
||||||
|
stm *state.StateManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(gr *graph.Graph, stm *state.StateManager) *Service {
|
||||||
|
return &Service{
|
||||||
|
gr: gr,
|
||||||
|
stm: stm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const topologyFragment = `
|
||||||
|
<script src="/assets/viz.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var viz = new Viz({ workerURL: '/assets/full.render.js' });
|
||||||
|
var xmlhttp = new XMLHttpRequest();
|
||||||
|
xmlhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4 && this.status == 200) {
|
||||||
|
var dot = this.responseText;
|
||||||
|
viz.renderSVGElement(dot)
|
||||||
|
.then(function(element) {
|
||||||
|
document.getElementById("graph").appendChild(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xmlhttp.open('GET', '/debug/graphviz');
|
||||||
|
xmlhttp.send();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<div id="graph" style="text-align: center;"></div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const switchportsFragment = `
|
||||||
|
<style type="text/css">
|
||||||
|
.table td,th {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 0.2em 0.4em 0.2em 0.4em;
|
||||||
|
}
|
||||||
|
.table th {
|
||||||
|
background-color: #c0c0c0;
|
||||||
|
}
|
||||||
|
.table {
|
||||||
|
background-color: #fff;
|
||||||
|
border-spacing: 0.2em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div>
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Switch</th>
|
||||||
|
<th>Port</th>
|
||||||
|
<th>Link State</th>
|
||||||
|
<th>Port Mode</th>
|
||||||
|
<th>MTU</th>
|
||||||
|
<th>Sync Status</th>
|
||||||
|
</tr>
|
||||||
|
{{range .Ports }}
|
||||||
|
{{ if .Managed }}
|
||||||
|
<tr>
|
||||||
|
{{ else }}
|
||||||
|
<tr style="opacity: 0.5">
|
||||||
|
{{ end}}
|
||||||
|
<td style="text-align: right;">{{ .Switch }}</td>
|
||||||
|
<td>{{ .Name }}</td>
|
||||||
|
{{ if eq .State "DOWN" }}
|
||||||
|
<td style="background-color: #ff3030;">{{ .State }}</td>
|
||||||
|
{{ else }}
|
||||||
|
<td>{{ .State }}</td>
|
||||||
|
{{ end }}
|
||||||
|
<td>{{ .Mode }}</td>
|
||||||
|
<td>{{ .MTU }}</td>
|
||||||
|
{{ if .Managed }}
|
||||||
|
<td style="background-color: #30ff30;">OK</td>
|
||||||
|
{{ else }}
|
||||||
|
<td><i>Unmanaged</i></td>
|
||||||
|
{{ end }}
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
func (s *Service) Setup(m *mirko.Mirko) {
|
||||||
|
assets := packr.NewBox("./assets")
|
||||||
|
m.HTTPMux().Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
|
||||||
|
m.HTTPMux().HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
|
||||||
|
statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
|
||||||
|
statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) statusHandleSwitchports(ctx context.Context) interface{} {
|
||||||
|
managedPorts := make(map[string]bool)
|
||||||
|
s.gr.Mu.RLock()
|
||||||
|
for _, sw := range s.gr.Switches {
|
||||||
|
for _, port := range sw.Ports {
|
||||||
|
managedPorts[sw.Name+"|"+port.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.gr.Mu.RUnlock()
|
||||||
|
|
||||||
|
s.stm.Mu.RLock()
|
||||||
|
defer s.stm.Mu.RUnlock()
|
||||||
|
|
||||||
|
res := struct {
|
||||||
|
Ports []*struct {
|
||||||
|
Switch string
|
||||||
|
Name string
|
||||||
|
State string
|
||||||
|
Mode string
|
||||||
|
Managed bool
|
||||||
|
MTU string
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
for _, sw := range s.stm.Switches {
|
||||||
|
for _, po := range sw.Ports {
|
||||||
|
state := "INVALID"
|
||||||
|
switch po.Proto.LinkState {
|
||||||
|
case pb.SwitchPort_LINKSTATE_DOWN:
|
||||||
|
state = "DOWN"
|
||||||
|
case pb.SwitchPort_LINKSTATE_UP:
|
||||||
|
state = "UP"
|
||||||
|
}
|
||||||
|
mode := "INVALID"
|
||||||
|
switch po.Proto.PortMode {
|
||||||
|
case pb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
|
||||||
|
mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
|
||||||
|
case pb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
|
||||||
|
mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
|
||||||
|
case pb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
|
||||||
|
mode = "GENERIC"
|
||||||
|
case pb.SwitchPort_PORTMODE_ROUTED:
|
||||||
|
mode = "ROUTED"
|
||||||
|
case pb.SwitchPort_PORTMODE_MANGLED:
|
||||||
|
mode = "MANGLED"
|
||||||
|
}
|
||||||
|
|
||||||
|
managed := managedPorts[sw.Name+"|"+po.Proto.Name]
|
||||||
|
res.Ports = append(res.Ports, &struct {
|
||||||
|
Switch string
|
||||||
|
Name string
|
||||||
|
State string
|
||||||
|
Mode string
|
||||||
|
Managed bool
|
||||||
|
MTU string
|
||||||
|
}{
|
||||||
|
Switch: sw.Name,
|
||||||
|
Name: po.Proto.Name,
|
||||||
|
State: state,
|
||||||
|
Mode: mode,
|
||||||
|
Managed: managed,
|
||||||
|
MTU: fmt.Sprintf("%d", po.Proto.Mtu),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) httpHandleGraphviz(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "graph G {\n")
|
||||||
|
fmt.Fprintf(w, " ranksep = 2\n")
|
||||||
|
fmt.Fprintf(w, " splines = polyline\n")
|
||||||
|
fmt.Fprintf(w, " rankdir = LR\n")
|
||||||
|
for _, machine := range s.gr.Machines {
|
||||||
|
portNames := []string{}
|
||||||
|
for _, port := range machine.Ports {
|
||||||
|
name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
|
||||||
|
portNames = append(portNames, name)
|
||||||
|
}
|
||||||
|
ports := strings.Join(portNames, "|")
|
||||||
|
fmt.Fprintf(w, " %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
|
||||||
|
}
|
||||||
|
for _, sw := range s.gr.Switches {
|
||||||
|
portNames := []string{}
|
||||||
|
portsOrdered := []*graph.SwitchPort{}
|
||||||
|
for _, port := range sw.Ports {
|
||||||
|
portsOrdered = append(portsOrdered, port)
|
||||||
|
}
|
||||||
|
sort.Slice(portsOrdered, func(i, j int) bool {
|
||||||
|
return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
|
||||||
|
})
|
||||||
|
for _, port := range portsOrdered {
|
||||||
|
name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
|
||||||
|
portNames = append(portNames, name)
|
||||||
|
}
|
||||||
|
ports := strings.Join(portNames, "|")
|
||||||
|
fmt.Fprintf(w, " %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
|
||||||
|
}
|
||||||
|
for _, machine := range s.gr.Machines {
|
||||||
|
for _, port := range machine.Ports {
|
||||||
|
if port.OtherEnd == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, " %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "}\n")
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
cpb "code.hackerspace.pl/q3k/topo/proto/config"
|
||||||
|
pb "code.hackerspace.pl/q3k/topo/proto/control"
|
||||||
|
|
||||||
|
"code.hackerspace.pl/q3k/hspki"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SwitchportState struct {
|
||||||
|
Proto *pb.SwitchPort
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchState struct {
|
||||||
|
Name string
|
||||||
|
Ports []*SwitchportState
|
||||||
|
Stub pb.SwitchControlClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwitchState) Fetch(ctx context.Context) error {
|
||||||
|
req := pb.GetPortsRequest{}
|
||||||
|
res, err := s.Stub.GetPorts(ctx, &req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetPorts: %v", err)
|
||||||
|
}
|
||||||
|
s.Ports = make([]*SwitchportState, len(res.Ports))
|
||||||
|
for i, port := range res.Ports {
|
||||||
|
s.Ports[i] = &SwitchportState{port}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StateManager struct {
|
||||||
|
Switches map[string]*SwitchState
|
||||||
|
Conns map[string]*grpc.ClientConn
|
||||||
|
Mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager() *StateManager {
|
||||||
|
return &StateManager{
|
||||||
|
Switches: make(map[string]*SwitchState),
|
||||||
|
Conns: make(map[string]*grpc.ClientConn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateManager) FetchState(ctx context.Context, conf *cpb.Config) error {
|
||||||
|
s.Mu.Lock()
|
||||||
|
defer s.Mu.Unlock()
|
||||||
|
for _, sw := range conf.Switch {
|
||||||
|
conn, ok := s.Conns[sw.ControlAddress]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
conn, err = grpc.Dial(sw.ControlAddress, hspki.WithClientHSPKI())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("when connecting to switch %s: %v", sw.Name, err)
|
||||||
|
}
|
||||||
|
s.Conns[sw.ControlAddress] = conn
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Switches[sw.Name] = &SwitchState{
|
||||||
|
Name: sw.Name,
|
||||||
|
Stub: pb.NewSwitchControlClient(conn),
|
||||||
|
}
|
||||||
|
err := s.Switches[sw.Name].Fetch(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%q.Fetch: %v", sw.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue