Merge branch 'master' of /home/q3k/Projects/hscloud/go/src/code.hackerspace.pl/q3k/topo

master
Serge Bazanski 2018-10-25 12:31:23 +01:00
commit ff9995ce61
11 changed files with 1931 additions and 0 deletions

File diff suppressed because one or more lines are too long

331
topo/assets/viz.js Normal file
View File

@ -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;
})));

32
topo/config.proto Normal file
View File

@ -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;
};

64
topo/control.proto Normal file
View File

@ -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);
};

213
topo/graph/graph.go Normal file
View File

@ -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
}

80
topo/main.go Normal file
View File

@ -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 {}
}

View File

@ -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,
}

View File

@ -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",
}

4
topo/proto/generate.go Normal file
View File

@ -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

224
topo/service.go Normal file
View File

@ -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")
}

76
topo/state/state.go Normal file
View File

@ -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
}