Skip to content

Commit eecc0c4

Browse files
committed
Allows non standard OVS setups
Changes the checks to be based around the availability of the command line OVS tools instead of the availability of the service installed under apt. This allows netplan to consume OVS when it is provided as part of another package such as in snap environments. This involves minor changes to lots of the OVS code and also modifies the unit tests as to handle OVS from both standard and nonstandard setups. Signed-off-by: MJ Ponsonby <mj.ponsonby@canonical.com>
1 parent 773ee70 commit eecc0c4

File tree

13 files changed

+320
-233
lines changed

13 files changed

+320
-233
lines changed

meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ find = find_program('find')
3232

3333
add_project_arguments(
3434
'-DSBINDIR="' + join_paths(get_option('prefix'), get_option('sbindir')) + '"',
35+
'-DPREFIX="' + get_option('prefix') + '"',
36+
'-DBINDIR="' + get_option('bindir') + '"',
3537
'-D_GNU_SOURCE',
3638
'-Wconversion',
3739
language: 'c')

netplan_cli/cli/ovs.py

Lines changed: 69 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
import os
2020
import subprocess
2121

22-
from .utils import systemctl_is_active, systemctl_is_installed
23-
24-
OPENVSWITCH_OVS_VSCTL = '/usr/bin/ovs-vsctl'
25-
OPENVSWITCH_OVSDB_SERVER_UNIT = 'ovsdb-server.service'
22+
OPENVSWITCH_OVS_VSCTL = 'ovs-vsctl'
2623
# Defaults for non-optional settings, as defined here:
2724
# http://www.openvswitch.org/ovs-vswitchd.conf.db.5.pdf
2825
DEFAULTS = {
@@ -46,25 +43,43 @@ class OvsDbServerNotInstalled(Exception):
4643
pass
4744

4845

46+
def _ovs_vsctl_path(): # pragma: nocover
47+
if os.path.exists('/snap/bin/'+OPENVSWITCH_OVS_VSCTL):
48+
return '/snap/bin/'+OPENVSWITCH_OVS_VSCTL
49+
else:
50+
return '/usr/bin/'+OPENVSWITCH_OVS_VSCTL
51+
52+
53+
OVS_VSCTL_PATH = _ovs_vsctl_path()
54+
55+
56+
def _ovs_installed(): # pragma: nocover
57+
return os.path.exists(OVS_VSCTL_PATH)
58+
59+
60+
def _ovs_active(): # pragma: nocover
61+
return subprocess.call([OVS_VSCTL_PATH, 'show'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
62+
63+
4964
def _del_col(type, iface, column, value):
5065
"""Cleanup values from a column (i.e. "column=value")"""
5166
default = DEFAULTS.get(column)
5267
if default is None:
5368
# removes the exact value only if it was set by netplan
54-
cmd = [OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, column, value]
69+
cmd = [OVS_VSCTL_PATH, 'remove', type, iface, column, value]
5570
logging.debug('Running: %s' % ' '.join(cmd))
5671
subprocess.check_call(cmd)
5772
elif default and default != value:
5873
# reset to default, if its not the default already
59-
cmd = [OPENVSWITCH_OVS_VSCTL, 'set', type, iface, '%s=%s' % (column, default)]
74+
cmd = [OVS_VSCTL_PATH, 'set', type, iface, '%s=%s' % (column, default)]
6075
logging.debug('Running: %s' % ' '.join(cmd))
6176
subprocess.check_call(cmd)
6277

6378

6479
def _del_dict(type, iface, column, key, value):
6580
"""Cleanup values from a dictionary (i.e. "column:key=value")"""
6681
# removes the exact value only if it was set by netplan
67-
cmd = [OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, column, '%s=\"%s\"' % (key, value)]
82+
cmd = [OVS_VSCTL_PATH, 'remove', type, iface, column, '%s=\"%s\"' % (key, value)]
6883
logging.debug('Running: %s' % ' '.join(cmd))
6984
subprocess.check_call(cmd)
7085

@@ -76,8 +91,8 @@ def _del_global(type, iface, key, value):
7691
iface = None
7792

7893
if del_cmd:
79-
args_get = [OPENVSWITCH_OVS_VSCTL, get_cmd]
80-
args_del = [OPENVSWITCH_OVS_VSCTL, del_cmd]
94+
args_get = [OVS_VSCTL_PATH, get_cmd]
95+
args_del = [OVS_VSCTL_PATH, del_cmd]
8196
if iface:
8297
args_get.append(iface)
8398
args_del.append(iface)
@@ -112,7 +127,7 @@ def clear_setting(type, iface, setting, value):
112127
else:
113128
_del_col(type, iface, split[1], value)
114129
# Cleanup the tag itself (i.e. "netplan/column[/key]")
115-
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, 'remove', type, iface, 'external-ids', setting])
130+
subprocess.check_call([OVS_VSCTL_PATH, 'remove', type, iface, 'external-ids', setting])
116131

117132

118133
def is_ovs_interface(iface, np_interface_dict):
@@ -129,11 +144,11 @@ def apply_ovs_cleanup(config_manager, ovs_old, ovs_current): # pragma: nocover
129144
Also filter for individual settings tagged netplan/<column>[/<key]=value
130145
in external-ids and clear them if they have been set by netplan.
131146
"""
132-
if not systemctl_is_installed(OPENVSWITCH_OVSDB_SERVER_UNIT):
133-
raise OvsDbServerNotInstalled("Cannot apply OVS cleanup: %s is 'not-found'" %
134-
OPENVSWITCH_OVSDB_SERVER_UNIT)
135-
if not systemctl_is_active(OPENVSWITCH_OVSDB_SERVER_UNIT):
136-
raise OvsDbServerNotRunning('{} is not running'.format(OPENVSWITCH_OVSDB_SERVER_UNIT))
147+
if not _ovs_installed():
148+
raise OvsDbServerNotInstalled("Cannot apply OpenvSwitch cleanup: ovs-vsctl is 'not-found'")
149+
150+
if not _ovs_active():
151+
raise OvsDbServerNotRunning('OpenvSwitch database is not running')
137152

138153
config_manager.parse()
139154
ovs_ifaces = set()
@@ -144,47 +159,42 @@ def apply_ovs_cleanup(config_manager, ovs_old, ovs_current): # pragma: nocover
144159
# Tear down old OVS interfaces, not defined in the current config.
145160
# Use 'del-br' on the Interface table, to delete any netplan created VLAN fake bridges.
146161
# Use 'del-bond-iface' on the Interface table, to delete netplan created patch port interfaces
147-
if os.path.isfile(OPENVSWITCH_OVS_VSCTL):
148-
# Step 1: Delete all interfaces, which are not part of the current OVS config
149-
for t in (('Port', 'del-port'), ('Bridge', 'del-br'), ('Interface', 'del-br')):
150-
out = subprocess.check_output([OPENVSWITCH_OVS_VSCTL, '--columns=name,external-ids',
151-
'-f', 'csv', '-d', 'bare', '--no-headings', 'list', t[0]],
152-
text=True)
153-
for line in out.splitlines():
154-
if 'netplan=true' in line:
155-
iface = line.split(',')[0]
156-
# Skip cleanup if this OVS interface is part of the current netplan OVS config
157-
if iface in ovs_ifaces:
158-
continue
159-
if t[0] == 'Interface' and subprocess.run([OPENVSWITCH_OVS_VSCTL, 'br-exists', iface]).returncode > 0:
160-
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, '--if-exists', 'del-bond-iface', iface])
161-
else:
162-
subprocess.check_call([OPENVSWITCH_OVS_VSCTL, '--if-exists', t[1], iface])
163-
164-
# Step 2: Clean up the settings of the remaining interfaces
165-
for t in ('Port', 'Bridge', 'Interface', 'Open_vSwitch', 'Controller'):
166-
cols = 'name,external-ids'
167-
if t == 'Open_vSwitch':
168-
cols = 'external-ids'
169-
elif t == 'Controller':
170-
cols = '_uuid,external-ids' # handle _uuid as if it would be the iface 'name'
171-
out = subprocess.check_output([OPENVSWITCH_OVS_VSCTL, '--columns=%s' % cols,
172-
'-f', 'csv', '-d', 'bare', '--no-headings', 'list', t],
173-
text=True)
174-
for line in out.splitlines():
175-
if 'netplan/' in line:
176-
iface = '.'
177-
extids = line
178-
if t != 'Open_vSwitch':
179-
iface, extids = line.split(',', 1)
180-
# Check each line (interface) if it contains any netplan tagged settings, e.g.:
181-
# ovs0,"iface-id=myhostname netplan=true netplan/external-ids/iface-id=myhostname"
182-
# ovs1,"netplan=true netplan/global/set-fail-mode=standalone netplan/mcast_snooping_enable=false"
183-
for entry in extids.strip('"').split(' '):
184-
if entry.startswith('netplan/') and '=' in entry:
185-
setting, val = entry.split('=', 1)
186-
clear_setting(t, iface, setting, val)
187-
188-
# Show the warning only if we are or have been working with OVS definitions
189-
elif ovs_old or ovs_current:
190-
logging.warning('ovs-vsctl is missing, cannot tear down old OpenVSwitch interfaces')
162+
# Step 1: Delete all interfaces, which are not part of the current OVS config
163+
for t in (('Port', 'del-port'), ('Bridge', 'del-br'), ('Interface', 'del-br')):
164+
out = subprocess.check_output([OVS_VSCTL_PATH, '--columns=name,external-ids',
165+
'-f', 'csv', '-d', 'bare', '--no-headings', 'list', t[0]],
166+
text=True)
167+
for line in out.splitlines():
168+
if 'netplan=true' in line:
169+
iface = line.split(',')[0]
170+
# Skip cleanup if this OVS interface is part of the current netplan OVS config
171+
if iface in ovs_ifaces:
172+
continue
173+
if t[0] == 'Interface' and subprocess.run([OVS_VSCTL_PATH, 'br-exists', iface]).returncode > 0:
174+
subprocess.check_call([OVS_VSCTL_PATH, '--if-exists', 'del-bond-iface', iface])
175+
else:
176+
subprocess.check_call([OVS_VSCTL_PATH, '--if-exists', t[1], iface])
177+
178+
# Step 2: Clean up the settings of the remaining interfaces
179+
for t in ('Port', 'Bridge', 'Interface', 'Open_vSwitch', 'Controller'):
180+
cols = 'name,external-ids'
181+
if t == 'Open_vSwitch':
182+
cols = 'external-ids'
183+
elif t == 'Controller':
184+
cols = '_uuid,external-ids' # handle _uuid as if it would be the iface 'name'
185+
out = subprocess.check_output([OVS_VSCTL_PATH, '--columns=%s' % cols,
186+
'-f', 'csv', '-d', 'bare', '--no-headings', 'list', t],
187+
text=True)
188+
for line in out.splitlines():
189+
if 'netplan/' in line:
190+
iface = '.'
191+
extids = line
192+
if t != 'Open_vSwitch':
193+
iface, extids = line.split(',', 1)
194+
# Check each line (interface) if it contains any netplan tagged settings, e.g.:
195+
# ovs0,"iface-id=myhostname netplan=true netplan/external-ids/iface-id=myhostname"
196+
# ovs1,"netplan=true netplan/global/set-fail-mode=standalone netplan/mcast_snooping_enable=false"
197+
for entry in extids.strip('"').split(' '):
198+
if entry.startswith('netplan/') and '=' in entry:
199+
setting, val = entry.split('=', 1)
200+
clear_setting(t, iface, setting, val)

0 commit comments

Comments
 (0)