Commit 6778c850 authored by Dietmar Maurer's avatar Dietmar Maurer

add GUI for openvswitch network

parent 1329a45f
......@@ -25,10 +25,20 @@ my $bond_mode_enum = [
'broadcast',
'802.3ad',
'balance-tlb',
'balance-alb'
'balance-alb',
'balance-slb', # OVS only
'balance-tcp', # OVS only
];
my $network_type_enum = ['bridge', 'bond', 'eth', 'alias',
'OVSBridge', 'OVSBond', 'OVSPort', 'OVSIntPort'];
my $confdesc = {
type => {
description => "Network interface type",
type => 'string',
enum => [@$network_type_enum, 'unknown'],
},
autostart => {
description => "Automatically start interface on boot.",
type => 'boolean',
......@@ -39,11 +49,32 @@ my $confdesc = {
optional => 1,
type => 'string', format => 'pve-iface-list',
},
ovs_ports => {
description => "Specify the iterfaces you want to add to your bridge.",
optional => 1,
type => 'string', format => 'pve-iface-list',
},
ovs_options => {
description => "OVS interface options.",
optional => 1,
type => 'string',
maxLength => 1024,
},
ovs_bridge => {
description => "The OVS bridge associated with a OVS port. This is required when you create an OVS port.",
optional => 1,
type => 'string', format => 'pve-iface',
},
slaves => {
description => "Specify the interfaces used by the bonding device.",
optional => 1,
type => 'string', format => 'pve-iface-list',
},
ovs_bonds => {
description => "Specify the interfaces used by the bonding device.",
optional => 1,
type => 'string', format => 'pve-iface-list',
},
bond_mode => {
description => "Bonding mode.",
optional => 1,
......@@ -92,7 +123,7 @@ __PACKAGE__->register_method({
type => {
description => "Only list specific interface types.",
type => 'string',
enum => ['bond', 'bridge', 'alias', 'eth'],
enum => $network_type_enum,
optional => 1,
},
},
......@@ -210,6 +241,19 @@ __PACKAGE__->register_method({
$param->{method} = $param->{address} ? 'static' : 'manual';
if ($param->{type} eq 'OVSIntPort' || $param->{type} eq 'OVSBond') {
my $brname = $param->{ovs_bridge};
raise_param_exc({ ovs_bridge => "parameter is required" }) if !$brname;
my $br = $config->{$brname};
raise_param_exc({ ovs_bridge => "bridge '$brname' does not exist" }) if !$br;
raise_param_exc({ ovs_bridge => "interface '$brname' is no OVS bridge" })
if $br->{type} ne 'OVSBridge';
my @ports = split (/\s+/, $br->{ovs_ports} || '');
$br->{ovs_ports} = join(' ', @ports, $iface)
if ! grep { $_ eq $iface } @ports;
}
$config->{$iface} = $param;
PVE::INotify::write_file('interfaces', $config);
......@@ -346,6 +390,19 @@ __PACKAGE__->register_method({
raise_param_exc({ iface => "interface does not exist" })
if !$config->{$param->{iface}};
my $d = $config->{$param->{iface}};
if ($d->{type} eq 'OVSIntPort' || $d->{type} eq 'OVSBond') {
if (my $brname = $d->{ovs_bridge}) {
if (my $br = $config->{$brname}) {
if ($br->{ovs_ports}) {
my @ports = split (/\s+/, $br->{ovs_ports});
my @new = grep { $_ ne $param->{iface} } @ports;
$br->{ovs_ports} = join(' ', @new);
}
}
}
}
delete $config->{$param->{iface}};
PVE::INotify::write_file('interfaces', $config);
......
......@@ -77,6 +77,12 @@ Ext.apply(Ext.form.field.VTypes, {
},
BondNameText: gettext('Format') + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999',
InterfaceName: function(v) {
return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
},
InterfaceNameText: gettext('Format') + ': [a-z][a-z0-9_]{1,20}',
QemuStartDate: function(v) {
return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v);
},
......@@ -160,6 +166,21 @@ Ext.define('PVE.Utils', { statics: {
return value;
},
network_iface_types: {
eth: gettext("Network Device"),
bridge: 'Linux Bridge',
bond: 'Linux Bond',
OVSBridge: 'OVS Bridge',
OVSBond: 'OVS Bond',
OVSPort: 'OVS Port',
OVSIntPort: 'OVS IntPort'
},
render_network_iface_type: function(value) {
return PVE.Utils.network_iface_types[value] ||
PVE.Utils.unknownText;
},
render_scsihw: function(value) {
if (!value) {
return PVE.Utils.defaultText + ' (LSI 53C895A)';
......
......@@ -2,9 +2,18 @@ Ext.define('PVE.form.BondModeSelector', {
extend: 'PVE.form.KVComboBox',
alias: ['widget.bondModeSelector'],
openvswitch: false,
initComponent: function() {
var me = this;
if (me.openvswitch) {
me.data = [
['balance-tcp', 'balance-tcp'],
['balance-slb', 'balance-slb'],
['active-backup', 'active-backup']
];
} else {
me.data = [
['balance-rr', 'balance-rr'],
['active-backup', 'active-backup'],
......@@ -14,6 +23,7 @@ Ext.define('PVE.form.BondModeSelector', {
['balance-tlb', 'balance-tlb'],
['balance-alb', 'balance-alb']
];
}
me.callParent();
}
......
......@@ -2,6 +2,8 @@ Ext.define('PVE.form.BridgeSelector', {
extend: 'PVE.form.ComboGrid',
alias: ['widget.PVE.form.BridgeSelector'],
bridgeType: 'bridge', // or OVSBridge
setNodename: function(nodename) {
var me = this;
......@@ -13,7 +15,8 @@ Ext.define('PVE.form.BridgeSelector', {
me.store.setProxy({
type: 'pve',
url: '/api2/json/nodes/' + me.nodename + '/network?type=bridge'
url: '/api2/json/nodes/' + me.nodename + '/network?type=' +
me.bridgeType
});
me.store.load();
......
......@@ -19,26 +19,38 @@ Ext.define('PVE.node.NetworkEdit', {
var iface_vtype;
if (me.iftype === 'bridge') {
me.subject = "Bridge";
iface_vtype = 'BridgeName';
} else if (me.iftype === 'bond') {
me.subject = "Bond";
iface_vtype = 'BondName';
} else if (me.iftype === 'eth' && !me.create) {
me.subject = gettext("Network Device");
iface_vtype = 'InterfaceName';
} else if (me.iftype === 'OVSBridge') {
iface_vtype = 'BridgeName';
} else if (me.iftype === 'OVSBond') {
iface_vtype = 'BondName';
} else if (me.iftype === 'OVSIntPort') {
iface_vtype = 'InterfaceName';
} else if (me.iftype === 'OVSPort') {
iface_vtype = 'InterfaceName';
} else {
throw "no known network device type specified";
console.log(me.iftype);
throw "unknown network device type specified";
}
var column2 = [
{
me.subject = PVE.Utils.render_network_iface_type(me.iftype);
var column2 = [];
if (!(me.iftype === 'OVSIntPort' || me.iftype === 'OVSPort' ||
me.iftype === 'OVSBond')) {
column2.push({
xtype: 'pvecheckbox',
fieldLabel: gettext('Autostart'),
name: 'autostart',
uncheckedValue: 0,
checked: me.create ? true : undefined
});
}
];
if (me.iftype === 'bridge') {
column2.push({
......@@ -46,6 +58,32 @@ Ext.define('PVE.node.NetworkEdit', {
fieldLabel: gettext('Bridge ports'),
name: 'bridge_ports'
});
} else if (me.iftype === 'OVSBridge') {
column2.push({
xtype: 'textfield',
fieldLabel: gettext('Bridge ports'),
name: 'ovs_ports'
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('OVS options'),
name: 'ovs_options'
});
} else if (me.iftype === 'OVSPort' || me.iftype === 'OVSIntPort') {
column2.push({
xtype: me.create ? 'PVE.form.BridgeSelector' : 'displayfield',
height: 22, // hack: set same height as text fields
fieldLabel: PVE.Utils.render_network_iface_type('OVSBridge'),
allowBlank: false,
nodename: nodename,
bridgeType: 'OVSBridge',
name: 'ovs_bridge'
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('OVS options'),
name: 'ovs_options'
});
} else if (me.iftype === 'bond') {
column2.push({
xtype: 'textfield',
......@@ -59,6 +97,21 @@ Ext.define('PVE.node.NetworkEdit', {
value: me.create ? 'balance-rr' : undefined,
allowBlank: false
});
} else if (me.iftype === 'OVSBond') {
column2.push({
xtype: me.create ? 'PVE.form.BridgeSelector' : 'displayfield',
height: 22, // hack: set same height as text fields
fieldLabel: PVE.Utils.render_network_iface_type('OVSBridge'),
allowBlank: false,
nodename: nodename,
bridgeType: 'OVSBridge',
name: 'ovs_bridge'
});
column2.push({
xtype: 'textfield',
fieldLabel: gettext('OVS options'),
name: 'ovs_options'
});
}
var url;
......@@ -73,6 +126,11 @@ Ext.define('PVE.node.NetworkEdit', {
}
var column1 = [
{
xtype: 'hiddenfield',
name: 'type',
value: me.iftype
},
{
xtype: me.create ? 'textfield' : 'displayfield',
fieldLabel: gettext('Name'),
......@@ -81,7 +139,30 @@ Ext.define('PVE.node.NetworkEdit', {
value: me.iface,
vtype: iface_vtype,
allowBlank: false
}
];
if (me.iftype === 'OVSPort') {
// nothing to edit
} else if (me.iftype === 'OVSBond') {
column1.push([
{
xtype: 'textfield',
fieldLabel: gettext('Slaves'),
name: 'ovs_bonds'
},
{
xtype: 'bondModeSelector',
fieldLabel: gettext('Mode'),
name: 'bond_mode',
openvswitch: true,
value: me.create ? 'active-backup' : undefined,
allowBlank: false
}
]);
} else {
column1.push([
{
xtype: 'pvetextfield',
deleteEmpty: !me.create,
......@@ -121,7 +202,8 @@ Ext.define('PVE.node.NetworkEdit', {
vtype: 'IPAddress',
name: 'gateway'
}
];
]);
}
Ext.applyIf(me, {
url: url,
......
......@@ -111,48 +111,85 @@ Ext.define('PVE.node.NetworkView', {
return record.data.bridge_ports;
} else if (value === 'bond') {
return record.data.slaves;
} else if (value === 'OVSBridge') {
return record.data.ovs_ports;
} else if (value === 'OVSBond') {
return record.data.ovs_bonds;
}
};
var find_next_iface_id = function(prefix) {
var next;
for (next = 0; next <= 9999; next++) {
if (!store.getById(prefix + next.toString())) {
break;
}
}
return prefix + next.toString();
};
Ext.apply(me, {
layout: 'border',
tbar: [
{
text: gettext('Create'),
menu: new Ext.menu.Menu({
plain: true,
items: [
{
text: 'Bridge',
text: PVE.Utils.render_network_iface_type('bridge'),
handler: function() {
var next;
for (next = 0; next <= 9999; next++) {
if (!store.data.get('vmbr' + next.toString())) {
break;
}
}
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'bridge',
iface_default: 'vmbr' + next.toString()
iface_default: find_next_iface_id('vmbr')
});
win.on('destroy', reload);
win.show();
}
},
{
text: 'Bond',
text: PVE.Utils.render_network_iface_type('bond'),
handler: function() {
var next;
for (next = 0; next <= 9999; next++) {
if (!store.data.get('bond' + next.toString())) {
break;
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'bond',
iface_default: find_next_iface_id('bond')
});
win.on('destroy', reload);
win.show();
}
}, '-',
{
text: PVE.Utils.render_network_iface_type('OVSBridge'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'OVSBridge',
iface_default: find_next_iface_id('vmbr')
});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.render_network_iface_type('OVSBond'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'OVSBond',
iface_default: find_next_iface_id('bond')
});
win.on('destroy', reload);
win.show();
}
},
{
text: PVE.Utils.render_network_iface_type('OVSIntPort'),
handler: function() {
var win = Ext.create('PVE.node.NetworkEdit', {
pveSelNode: me.pveSelNode,
iftype: 'bond',
iface_default: 'bond' + next.toString()
iftype: 'OVSIntPort'
});
win.on('destroy', reload);
win.show();
......@@ -194,6 +231,13 @@ Ext.define('PVE.node.NetworkView', {
sortable: true,
dataIndex: 'iface'
},
{
header: gettext('Type'),
width: 100,
sortable: true,
renderer: PVE.Utils.render_network_iface_type,
dataIndex: 'type'
},
{
xtype: 'booleancolumn',
header: gettext('Active'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment