'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',
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,
type => {
description => "Only list specific interface types.",
type => 'string',
- enum => ['bond', 'bridge', 'alias', 'eth'],
+ enum => $network_type_enum,
optional => 1,
},
},
$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);
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);
},
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);
},
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)';
extend: 'PVE.form.KVComboBox',
alias: ['widget.bondModeSelector'],
+ openvswitch: false,
+
initComponent: function() {
var me = this;
- me.data = [
- ['balance-rr', 'balance-rr'],
- ['active-backup', 'active-backup'],
- ['balance-xor', 'balance-xor'],
- ['broadcast', 'broadcast'],
- ['802.3ad', '802.3ad'],
- ['balance-tlb', 'balance-tlb'],
- ['balance-alb', 'balance-alb']
- ];
+ 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'],
+ ['balance-xor', 'balance-xor'],
+ ['broadcast', 'broadcast'],
+ ['802.3ad', '802.3ad'],
+ ['balance-tlb', 'balance-tlb'],
+ ['balance-alb', 'balance-alb']
+ ];
+ }
me.callParent();
}
extend: 'PVE.form.ComboGrid',
alias: ['widget.PVE.form.BridgeSelector'],
+ bridgeType: 'bridge', // or OVSBridge
+
setNodename: function(nodename) {
var me = this;
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();
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({
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',
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;
}
var column1 = [
+ {
+ xtype: 'hiddenfield',
+ name: 'type',
+ value: me.iftype
+ },
{
xtype: me.create ? 'textfield' : 'displayfield',
fieldLabel: gettext('Name'),
value: me.iface,
vtype: iface_vtype,
allowBlank: false
- },
- {
- xtype: 'pvetextfield',
- deleteEmpty: !me.create,
- fieldLabel: gettext('IP address'),
- vtype: 'IPAddress',
- name: 'address'
- },
- {
- xtype: 'pvetextfield',
- deleteEmpty: !me.create,
- fieldLabel: gettext('Subnet mask'),
- vtype: 'IPAddress',
- name: 'netmask',
- validator: function(value) {
- /*jslint confusion: true */
- if (!me.items) {
- return true;
- }
- var address = me.down('field[name=address]').getValue();
- if (value !== '') {
- if (address === '') {
- return "Subnet mask requires option 'IP address'";
+ }
+ ];
+
+ 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,
+ fieldLabel: gettext('IP address'),
+ vtype: 'IPAddress',
+ name: 'address'
+ },
+ {
+ xtype: 'pvetextfield',
+ deleteEmpty: !me.create,
+ fieldLabel: gettext('Subnet mask'),
+ vtype: 'IPAddress',
+ name: 'netmask',
+ validator: function(value) {
+ /*jslint confusion: true */
+ if (!me.items) {
+ return true;
}
- } else {
- if (address !== '') {
- return "Option 'IP address' requires a subnet mask";
+ var address = me.down('field[name=address]').getValue();
+ if (value !== '') {
+ if (address === '') {
+ return "Subnet mask requires option 'IP address'";
+ }
+ } else {
+ if (address !== '') {
+ return "Option 'IP address' requires a subnet mask";
+ }
}
- }
- return true;
+ return true;
+ }
+ },
+ {
+ xtype: 'pvetextfield',
+ deleteEmpty: !me.create,
+ fieldLabel: gettext('Gateway'),
+ vtype: 'IPAddress',
+ name: 'gateway'
}
- },
- {
- xtype: 'pvetextfield',
- deleteEmpty: !me.create,
- fieldLabel: gettext('Gateway'),
- vtype: 'IPAddress',
- name: 'gateway'
- }
- ];
+ ]);
+ }
Ext.applyIf(me, {
url: url,
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: 'bond' + next.toString()
+ 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: 'OVSIntPort'
+ });
+ win.on('destroy', reload);
+ win.show();
+ }
+ }
]
})
}, ' ',
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'),