Merge lp:~raharper/curtin/trunk.more-ipv6 into lp:~curtin-dev/curtin/trunk

Proposed by Ryan Harper
Status: Merged
Merged at revision: 421
Proposed branch: lp:~raharper/curtin/trunk.more-ipv6
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 3212 lines (+1750/-914)
30 files modified
curtin/commands/apply_net.py (+155/-1)
curtin/commands/curthooks.py (+4/-51)
curtin/net/__init__.py (+67/-30)
curtin/net/network_state.py (+45/-1)
examples/network-ipv6-bond-vlan.yaml (+56/-0)
examples/tests/basic_network_static_ipv6.yaml (+22/-0)
examples/tests/network_alias.yaml (+125/-0)
examples/tests/network_mtu.yaml (+88/-0)
examples/tests/network_source_ipv6.yaml (+31/-0)
examples/tests/vlan_network_ipv6.yaml (+92/-0)
tests/unittests/test_net.py (+54/-13)
tests/vmtests/helpers.py (+129/-166)
tests/vmtests/test_basic.py (+17/-39)
tests/vmtests/test_bcache_basic.py (+5/-8)
tests/vmtests/test_bonding.py (+0/-205)
tests/vmtests/test_mdadm_bcache.py (+9/-11)
tests/vmtests/test_multipath.py (+5/-13)
tests/vmtests/test_network.py (+205/-352)
tests/vmtests/test_network_alias.py (+40/-0)
tests/vmtests/test_network_bonding.py (+63/-0)
tests/vmtests/test_network_enisource.py (+91/-0)
tests/vmtests/test_network_ipv6.py (+53/-0)
tests/vmtests/test_network_ipv6_enisource.py (+26/-0)
tests/vmtests/test_network_ipv6_static.py (+42/-0)
tests/vmtests/test_network_ipv6_vlan.py (+34/-0)
tests/vmtests/test_network_mtu.py (+155/-0)
tests/vmtests/test_network_static.py (+44/-0)
tests/vmtests/test_network_vlan.py (+77/-0)
tests/vmtests/test_raid5_bcache.py (+5/-8)
tests/vmtests/test_uefi_basic.py (+11/-16)
To merge this branch: bzr merge lp:~raharper/curtin/trunk.more-ipv6
Reviewer Review Type Date Requested Status
Scott Moser (community) Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+304027@code.launchpad.net

Commit message

curtin/net: overhaul of eni rendering to handle mixed ipv4/ipv6 configurations

To ensure complete ipv4/ipv6 support for advanced and stacked configurations update
how curtin.net renders /etc/network/interfaces for different releases (precise -> yakkety)
ifupdown has subtle issues with various networking features and curtin needs to ensure
consistent behavior.

- Propery handle emitting the 'auto' control tag for stacked interfaces, like vlans over bonds
- Workaround LP:1609367 by rendering ifupdown hooks to handle the various cases. This works generically in all ubuntu releases
- Drop the use of ipv4 alias interfaces (eth0:1, eth0:2) and instead just add additional e/n/i stanzas. ifupdown already uses iproute2's /sbin/ip which supports adding additional ip addresses to an interface without the use of the v4-only interface alias structure. This provides consistent behavior for all types of interfaces (physical, vlan, bonds, and stacked interfaces) across all releases. Two side-effects: 1) users can no longer `ifdown eth0:1` to remove a single ip address from an interface; if down eth0 will take _all_ ip addresses on that interface. 2) ifconfig output only shows *one* ipv4 address, so users will need to use /sbin/ip addr show <interface> to see all ip addresses assigned to an interface.
- Restructure all of the common network testcases into a single class TestNetworkTestBaseAbs, all varients testing network inherit from this class and override only the config file and any special case test-cases and file collection
- Global replace of testcase use of 'with open' and instead use load_collect_file()
- Add ip_a_to_dict parser for `/sbin/ip a` output

Description of the change

curtin/net: overhaul eni rendering to handle mixed ipv4/ipv6 configurations

To ensure complete ipv4/ipv6 support for advanced and stacked
configurations update how curtin.net renders /etc/network/interfaces for
different releases (precise -> yakkety) ifupdown has subtle issues with
various networking features and curtin needs to ensure consistent
behavior.

- Properly handle emitting the 'auto' control tag for stacked interfaces,
  like vlans over bonds
- Workaround LP: #1609367 by rendering ifupdown hooks to handle the various
  cases. This works generically in all ubuntu releases.
- Drop the use of ipv4 alias interfaces (eth0:1, eth0:2) and instead just
  add additional e/n/i stanzas. ifupdown already uses iproute2's /sbin/ip
  which supports adding additional ip addresses to an interface without the
  use of the v4-only interface alias structure. This provides consistent
  behavior for all types of interfaces (physical, vlan, bonds, and stacked
  interfaces) across all releases.

  Two side-effects:
  1) users can no longer `ifdown eth0:1` to remove a single ip address
     from an interface; if down eth0 will take _all_ ip addresses on that
     interface.
  2) ifconfig output only shows *one* ipv4 address, so users will
     need to use 'ip addr show <interface>' to see all ip addresses
     assigned to an interface.
- Restructure all of the common network testcases into a single class
  TestNetworkTestBaseAbs, all variants testing network inherit from this
  class and override only the config file and any special case test-cases
  and file collection
- Global replace of testcase use of 'with open' and instead use
  load_collect_file()
- Add ip_a_to_dict parser for `/sbin/ip a` output

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
lp:~raharper/curtin/trunk.more-ipv6 updated
437. By Ryan Harper

merge from trunk

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Hi Ryan,
great work - a few minor comments and discussion points added inline.

Revision history for this message
Ryan Harper (raharper) wrote :
Download full text (12.9 KiB)

On Fri, Aug 26, 2016 at 3:59 AM, ChristianEhrhardt <
<email address hidden>> wrote:

> Hi Ryan,
> great work - a few minor comments and discussion points added inline.
>

Thanks for the review!

>
> Diff comments:
>
> > === modified file 'curtin/commands/apply_net.py'
> > --- curtin/commands/apply_net.py 2016-07-13 07:50:49 +0000
> > +++ curtin/commands/apply_net.py 2016-08-25 23:32:25 +0000
> > @@ -26,6 +26,54 @@
> >
> > LOG = log.LOG
> >
> > +IFUPDOWN_IPV6_MTU_PRE_HOOK = """#!/bin/sh -x
>
> Maybe play it even safer with -e to better fail than echoing potentially
> weird stuff to the mtu file.
>
> Same for line 23
>

Yeah, actually should drop the -x; I'll look at other ifupdown hooks and
see what they use.

>
> > +# injected by curtin installer
> > +
> > +[ "$IFACE" != "lo" ] || exit 0
> > +
> > +# Trigger only if MTU configured
> > +[ -n "$IF_MTU" ] || exit 0
> > +
> > +CUR_DEV_MTU=$(/bin/cat /sys/class/net/$IFACE/mtu ||:)
> > +CUR_IPV6_MTU=$(sysctl -n net.ipv6.conf.$IFACE.mtu ||:)
> > +[ -n "$CUR_DEV_MTU" ] && echo $CUR_DEV_MTU > /run/network/$IFACE_dev.mtu
> > +[ -n "$CUR_IPV6_MTU" ] && echo $CUR_IPV6_MTU >
> /run/network/$IFACE_ipv6.mtu
> > +exit 0
> > +"""
> > +
> > +IFUPDOWN_IPV6_MTU_POST_HOOK = """#!/bin/sh -x
> > +# injected by curtin installer
> > +
> > +[ "$IFACE" != "lo" ] || exit 0
> > +
> > +# Trigger only if MTU configured
> > +[ -n "$IF_MTU" ] || exit 0
> > +
> > +PRE_DEV_MTU=$(cat /run/network/$IFACE_dev.mtu)
> > +CUR_DEV_MTU=$(/bin/cat /sys/class/net/$IFACE/mtu)
> > +PRE_IPV6_MTU=$(cat /run/network/$IFACE_ipv6.mtu)
>
> I'm not sure if ${var} references are compliant, but here it would really
> enhance readability and maintainability. e.g. at ${IFACE}_ipv6 which would
> make is clearer where var and string meet.
>

It appears that ${VAR} works; most of the hooks don't use that; not
entirely sure why. I can update.

>
> > +CUR_IPV6_MTU=$(sysctl -n net.ipv6.conf.$IFACE.mtu)
> > +
> > +if [ "$ADDRFAM" = "inet6" ]; then
> > + # We need to check the underlying interface MTU and
> > + # raise it if the IPV6 mtu is larger
> > + if [ $CUR_DEV_MTU -lt $IF_MTU ]; then
> > + /bin/ip link set $IFACE mtu $IF_MTU
> > + fi
> > + /sbin/sysctl -q -e -w net.ipv6.conf.$IFACE.mtu=$IF_MTU
>
> I'm not sure if it would be wiser to put all the binary paths into an
> initial INIT phase.
> Probably with a which or anything like it, just for the case that ip,
> sysctl or others ever move to a different place.
> A which would make it fully dynamic, a central definition easier to
> maintain at one place.
> Open for discussion thou since it can have drawbacks like resolving to the
> wrong one in the worst case.
>

All of the existing tooks (and ifupdown itself) call via full path.

>
> > +
> > +elif [ "$ADDRFAM" = "inet" ]; then
> > + # handle the clobber case where inet mtu changes v6 mtu
> > + # ifupdown will already have set dev mtu, so lower mtu
> > + # if needed. If v6 mtu was larger, it get's clamped down
> > + # to the dev MTU value.
> > + if [ $PRE_IPV6_MTU -lt $CUR_IPV6_MTU ]; then
> > + /sbin/sysctl -q -e -w net.ipv6.conf.$IFACE.mtu=$PRE_IPV6_MTU
> > + fi
> > +fi
> > +exit 0
> > +"""
> > +
> >
> > ...

Revision history for this message
Scott Moser (smoser) wrote :

NITPICK: i reformatted your commit message to keep line lengths < 74 and some spelling.

i like the general idea, and the fact that you added some good vmtest tests.

Revision history for this message
Ryan Harper (raharper) wrote :
Download full text (15.1 KiB)

On Fri, Aug 26, 2016 at 9:23 AM, Scott Moser <email address hidden> wrote:

> NITPICK: i reformatted your commit message to keep line lengths < 74 and
> some spelling.
>
>
> i like the general idea, and the fact that you added some good vmtest
> tests.
>
> Diff comments:
>
> > === modified file 'curtin/commands/apply_net.py'
> > --- curtin/commands/apply_net.py 2016-07-13 07:50:49 +0000
> > +++ curtin/commands/apply_net.py 2016-08-25 23:32:25 +0000
> > @@ -26,6 +26,54 @@
> >
> > LOG = log.LOG
> >
> > +IFUPDOWN_IPV6_MTU_PRE_HOOK = """#!/bin/sh -x
>
> as much as i *hate* set -e, i agree here. either we need to run with -e,
> or otherwise correctly exit failure on failure rather than barreling on.
>

ACK

>
> > +# injected by curtin installer
> > +
> > +[ "$IFACE" != "lo" ] || exit 0
> > +
> > +# Trigger only if MTU configured
> > +[ -n "$IF_MTU" ] || exit 0
> > +
> > +CUR_DEV_MTU=$(/bin/cat /sys/class/net/$IFACE/mtu ||:)
>
> [ ! -e /sys/class/net/$IFACE/mtu ] || read CUR_DEV_MTU
> /sys/class/net/$IFACE/mtu
>

Nice, will change.

>
> > +CUR_IPV6_MTU=$(sysctl -n net.ipv6.conf.$IFACE.mtu ||:)
> > +[ -n "$CUR_DEV_MTU" ] && echo $CUR_DEV_MTU > /run/network/$IFACE_dev.mtu
> > +[ -n "$CUR_IPV6_MTU" ] && echo $CUR_IPV6_MTU >
> /run/network/$IFACE_ipv6.mtu
> > +exit 0
> > +"""
> > +
> > +IFUPDOWN_IPV6_MTU_POST_HOOK = """#!/bin/sh -x
> > +# injected by curtin installer
> > +
> > +[ "$IFACE" != "lo" ] || exit 0
> > +
> > +# Trigger only if MTU configured
> > +[ -n "$IF_MTU" ] || exit 0
> > +
> > +PRE_DEV_MTU=$(cat /run/network/$IFACE_dev.mtu)
>
> +one on christian's {} i know its obnoxious, but there are times when
> doing something like above ends up referencing a variable like '$IFACE_dev'
> rather than ${IFACE}_dev. i have never been able to find out a pattern on
> it, but its why i alays surround variables names. Also makes search and
> replace for var names easier.
>

I entirely agree with you; I was keeping with existing style of ifupdown
hooks. But I'm happy to do things safer.

>
> > +CUR_DEV_MTU=$(/bin/cat /sys/class/net/$IFACE/mtu)
>
> read CUR_DEV_MTU < /sys/class/net/$IFACE/mtu
> is lots faster and equivalent.
>
> i realize this is picking on the order of hundreths of a second, but this
> blocks network coming up which blocks boot.
>

Understood, worth a change.

>
> > +PRE_IPV6_MTU=$(cat /run/network/$IFACE_ipv6.mtu)
> > +CUR_IPV6_MTU=$(sysctl -n net.ipv6.conf.$IFACE.mtu)
>
> echo > /sys/class/net .... ? rather than sysctl ? is there a reason for
> one over the other ?
> i thought you recently told me sysctl was just doing that.
>

I can replace the sysctl -n with a read; it's somewhat non-obvious
that sysctl net. implies /proc/sys/net . But it saves the exec overhead

>
> > +
> > +if [ "$ADDRFAM" = "inet6" ]; then
> > + # We need to check the underlying interface MTU and
> > + # raise it if the IPV6 mtu is larger
> > + if [ $CUR_DEV_MTU -lt $IF_MTU ]; then
> > + /bin/ip link set $IFACE mtu $IF_MTU
> > + fi
> > + /sbin/sysctl -q -e -w net.ipv6.conf.$IFACE.mtu=$IF_MTU
>
> even better, just do not hard code paths.
> if /sbin/ is not in your path you are probably foobarred in other ways
> you've not considered.
> if /...

lp:~raharper/curtin/trunk.more-ipv6 updated
438. By Ryan Harper

Addressed review comments

curtin.commands.apply_net:
 - use /bin/sh -e for hooks
 - use ${VAR} for references
 - replace execs with read and echo
 - remove hardcoded paths to binaries
 - make use of util.target_path for safer path construction
 - move debugging output to log.debug

curtin.net:
- iface_start_entry: drop unused index parameter
- drop unused functions: subnet_is_ipv4, list_ipv4_subnets,
  iface_add_postup
- Add comment to document how we skip repeated 'auto $IFACE' lines
  and why we don't

tests.vmtests.helpers
- drop unused ifconfig-a parsing
- update ip_a_to_dict comment to display parsed output

tests.vmtests.test_network_enisource
- replace use of ifconfig_to_dict with ip_a_to_dict

439. By Ryan Harper

Fix use of util.target_path(); workaround dash bug lp:598275

440. By Ryan Harper

fix trailing whitespace

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
lp:~raharper/curtin/trunk.more-ipv6 updated
441. By Ryan Harper

mtuhook: switch to bash to use read vs. sysctl exec

442. By Ryan Harper

vmtest:multipath fix up readlink on holders data

443. By Ryan Harper

vmtests:uefi fix broke test, handle variance in sys/firmware/efi contents

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
lp:~raharper/curtin/trunk.more-ipv6 updated
444. By Ryan Harper

merge from trunk

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

This looks as good to me as something can look for something so large.
You have added some tests and such, so LGTM.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'curtin/commands/apply_net.py'
--- curtin/commands/apply_net.py 2016-08-22 19:39:06 +0000
+++ curtin/commands/apply_net.py 2016-08-29 20:01:45 +0000
@@ -26,6 +26,57 @@
2626
27LOG = log.LOG27LOG = log.LOG
2828
29IFUPDOWN_IPV6_MTU_PRE_HOOK = """#!/bin/bash -e
30# injected by curtin installer
31
32[ "${IFACE}" != "lo" ] || exit 0
33
34# Trigger only if MTU configured
35[ -n "${IF_MTU}" ] || exit 0
36
37read CUR_DEV_MTU </sys/class/net/${IFACE}/mtu ||:
38read CUR_IPV6_MTU </proc/sys/net/ipv6/conf/${IFACE}/mtu ||:
39[ -n "${CUR_DEV_MTU}" ] && echo ${CUR_DEV_MTU} > /run/network/${IFACE}_dev.mtu
40[ -n "${CUR_IPV6_MTU}" ] &&
41 echo ${CUR_IPV6_MTU} > /run/network/${IFACE}_ipv6.mtu
42exit 0
43"""
44
45IFUPDOWN_IPV6_MTU_POST_HOOK = """#!/bin/bash -e
46# injected by curtin installer
47
48[ "${IFACE}" != "lo" ] || exit 0
49
50# Trigger only if MTU configured
51[ -n "${IF_MTU}" ] || exit 0
52
53read PRE_DEV_MTU </run/network/${IFACE}_dev.mtu ||:
54read CUR_DEV_MTU </sys/class/net/${IFACE}/mtu ||:
55read PRE_IPV6_MTU </run/network/${IFACE}_ipv6.mtu ||:
56read CUR_IPV6_MTU </proc/sys/net/ipv6/conf/${IFACE}/mtu ||:
57
58if [ "${ADDRFAM}" = "inet6" ]; then
59 # We need to check the underlying interface MTU and
60 # raise it if the IPV6 mtu is larger
61 if [ ${CUR_DEV_MTU} -lt ${IF_MTU} ]; then
62 ip link set ${IFACE} mtu ${IF_MTU}
63 fi
64 # sysctl -q -e -w net.ipv6.conf.${IFACE}.mtu=${IF_MTU}
65 echo ${IF_MTU} >/proc/sys/net/ipv6/conf/${IFACE}/mtu ||:
66
67elif [ "${ADDRFAM}" = "inet" ]; then
68 # handle the clobber case where inet mtu changes v6 mtu.
69 # ifupdown will already have set dev mtu, so lower mtu
70 # if needed. If v6 mtu was larger, it get's clamped down
71 # to the dev MTU value.
72 if [ ${PRE_IPV6_MTU} -lt ${CUR_IPV6_MTU} ]; then
73 # sysctl -q -e -w net.ipv6.conf.${IFACE}.mtu=${PRE_IPV6_MTU}
74 echo ${PRE_IPV6_MTU} >/proc/sys/net/ipv6/conf/${IFACE}/mtu ||:
75 fi
76fi
77exit 0
78"""
79
2980
30def apply_net(target, network_state=None, network_config=None):81def apply_net(target, network_state=None, network_config=None):
31 if network_state is None and network_config is None:82 if network_state is None and network_config is None:
@@ -45,6 +96,108 @@
4596
46 net.render_network_state(target=target, network_state=ns)97 net.render_network_state(target=target, network_state=ns)
4798
99 _maybe_remove_legacy_eth0(target)
100 LOG.info('Attempting to remove ipv6 privacy extensions')
101 _disable_ipv6_privacy_extensions(target)
102 _patch_ifupdown_ipv6_mtu_hook(target)
103
104
105def _patch_ifupdown_ipv6_mtu_hook(target,
106 prehookfn="etc/network/if-pre-up.d/mtuipv6",
107 posthookfn="etc/network/if-up.d/mtuipv6"):
108
109 contents = {
110 'prehook': IFUPDOWN_IPV6_MTU_PRE_HOOK,
111 'posthook': IFUPDOWN_IPV6_MTU_POST_HOOK,
112 }
113
114 hookfn = {
115 'prehook': prehookfn,
116 'posthook': posthookfn,
117 }
118
119 for hook in ['prehook', 'posthook']:
120 fn = hookfn[hook]
121 cfg = util.target_path(target, path=fn)
122 LOG.info('Injecting fix for ipv6 mtu settings: %s', cfg)
123 util.write_file(cfg, contents[hook], mode=0o755)
124
125
126def _disable_ipv6_privacy_extensions(target,
127 path="etc/sysctl.d/10-ipv6-privacy.conf"):
128
129 """Ubuntu server image sets a preference to use IPv6 privacy extensions
130 by default; this races with the cloud-image desire to disable them.
131 Resolve this by allowing the cloud-image setting to win. """
132
133 cfg = util.target_path(target, path=path)
134 if not os.path.exists(cfg):
135 LOG.warn('Failed to find ipv6 privacy conf file %s', cfg)
136 return
137
138 bmsg = "Disabling IPv6 privacy extensions config may not apply."
139 try:
140 contents = util.load_file(cfg)
141 known_contents = ["net.ipv6.conf.all.use_tempaddr = 2",
142 "net.ipv6.conf.default.use_tempaddr = 2"]
143 lines = [f.strip() for f in contents.splitlines()
144 if not f.startswith("#")]
145 if lines == known_contents:
146 LOG.info('deleting file: %s', cfg)
147 util.del_file(cfg)
148 msg = "removed %s with known contents" % cfg
149 curtin_contents = '\n'.join(
150 ["# IPv6 Privacy Extensions (RFC 4941)",
151 "# Disabled by curtin",
152 "# net.ipv6.conf.all.use_tempaddr = 2",
153 "# net.ipv6.conf.default.use_tempaddr = 2"])
154 util.write_file(cfg, curtin_contents)
155 else:
156 LOG.info('skipping, content didnt match')
157 LOG.debug("found content:\n%s", lines)
158 LOG.debug("expected contents:\n%s", known_contents)
159 msg = (bmsg + " '%s' exists with user configured content." % cfg)
160 except:
161 msg = bmsg + " %s exists, but could not be read." % cfg
162 LOG.exception(msg)
163 return
164
165
166def _maybe_remove_legacy_eth0(target,
167 path="etc/network/interfaces.d/eth0.cfg"):
168 """Ubuntu cloud images previously included a 'eth0.cfg' that had
169 hard coded content. That file would interfere with the rendered
170 configuration if it was present.
171
172 if the file does not exist do nothing.
173 If the file exists:
174 - with known content, remove it and warn
175 - with unknown content, leave it and warn
176 """
177
178 cfg = util.target_path(target, path=path)
179 if not os.path.exists(cfg):
180 LOG.warn('Failed to find legacy network conf file %s', cfg)
181 return
182
183 bmsg = "Dynamic networking config may not apply."
184 try:
185 contents = util.load_file(cfg)
186 known_contents = ["auto eth0", "iface eth0 inet dhcp"]
187 lines = [f.strip() for f in contents.splitlines()
188 if not f.startswith("#")]
189 if lines == known_contents:
190 util.del_file(cfg)
191 msg = "removed %s with known contents" % cfg
192 else:
193 msg = (bmsg + " '%s' exists with user configured content." % cfg)
194 except:
195 msg = bmsg + " %s exists, but could not be read." % cfg
196 LOG.exception(msg)
197 return
198
199 LOG.warn(msg)
200
48201
49def apply_net_main(args):202def apply_net_main(args):
50 # curtin apply_net [--net-state=/config/netstate.yml] [--target=/]203 # curtin apply_net [--net-state=/config/netstate.yml] [--target=/]
@@ -76,6 +229,7 @@
76 apply_net(target=state['target'],229 apply_net(target=state['target'],
77 network_state=state['network_state'],230 network_state=state['network_state'],
78 network_config=state['network_config'])231 network_config=state['network_config'])
232
79 except Exception:233 except Exception:
80 LOG.exception('failed to apply network config')234 LOG.exception('failed to apply network config')
81 return 1235 return 1
@@ -91,7 +245,7 @@
91 'metavar': 'NETSTATE', 'action': 'store',245 'metavar': 'NETSTATE', 'action': 'store',
92 'default': os.environ.get('OUTPUT_NETWORK_STATE')}),246 'default': os.environ.get('OUTPUT_NETWORK_STATE')}),
93 (('-t', '--target'),247 (('-t', '--target'),
94 {'help': ('target filesystem root to add swap file to. '248 {'help': ('target filesystem root to configure networking to. '
95 'default is env["TARGET_MOUNT_POINT"]'),249 'default is env["TARGET_MOUNT_POINT"]'),
96 'metavar': 'TARGET', 'action': 'store',250 'metavar': 'TARGET', 'action': 'store',
97 'default': os.environ.get('TARGET_MOUNT_POINT')}),251 'default': os.environ.get('TARGET_MOUNT_POINT')}),
98252
=== modified file 'curtin/commands/curthooks.py'
--- curtin/commands/curthooks.py 2016-07-29 17:19:20 +0000
+++ curtin/commands/curthooks.py 2016-08-29 20:01:45 +0000
@@ -28,9 +28,8 @@
28from curtin.log import LOG28from curtin.log import LOG
29from curtin import swap29from curtin import swap
30from curtin import util30from curtin import util
31from curtin import net
32from curtin.reporter import events31from curtin.reporter import events
33from curtin.commands import apt_config32from curtin.commands import apply_net, apt_config
3433
35from . import populate_one_subcmd34from . import populate_one_subcmd
3635
@@ -109,42 +108,6 @@
109 shutil.move(local_conf, local_conf + ".old")108 shutil.move(local_conf, local_conf + ".old")
110109
111110
112def _maybe_remove_legacy_eth0(target,
113 path="/etc/network/interfaces.d/eth0.cfg"):
114 """Ubuntu cloud images previously included a 'eth0.cfg' that had
115 hard coded content. That file would interfere with the rendered
116 configuration if it was present.
117
118 if the file does not exist do nothing.
119 If the file exists:
120 - with known content, remove it and warn
121 - with unknown content, leave it and warn
122 """
123
124 cfg = os.path.sep.join([target, path])
125 if not os.path.exists(cfg):
126 LOG.warn('Failed to find legacy conf file %s', cfg)
127 return
128
129 bmsg = "Dynamic networking config may not apply."
130 try:
131 contents = util.load_file(cfg)
132 known_contents = ["auto eth0", "iface eth0 inet dhcp"]
133 lines = [f.strip() for f in contents.splitlines()
134 if not f.startswith("#")]
135 if lines == known_contents:
136 util.del_file(cfg)
137 msg = "removed %s with known contents" % cfg
138 else:
139 msg = (bmsg + " '%s' exists with user configured content." % cfg)
140 except:
141 msg = bmsg + " %s exists, but could not be read." % cfg
142 LOG.exception(msg)
143 return
144
145 LOG.warn(msg)
146
147
148def setup_zipl(cfg, target):111def setup_zipl(cfg, target):
149 if platform.machine() != 's390x':112 if platform.machine() != 's390x':
150 return113 return
@@ -411,7 +374,6 @@
411374
412375
413def apply_networking(target, state):376def apply_networking(target, state):
414 netstate = state.get('network_state')
415 netconf = state.get('network_config')377 netconf = state.get('network_config')
416 interfaces = state.get('interfaces')378 interfaces = state.get('interfaces')
417379
@@ -422,22 +384,13 @@
422 return True384 return True
423 return False385 return False
424386
425 ns = None387 if is_valid_src(netconf):
426 if is_valid_src(netstate):388 LOG.info("applying network_config")
427 LOG.debug("applying network_state")389 apply_net.apply_net(target, network_state=None, network_config=netconf)
428 ns = net.network_state.from_state_file(netstate)
429 elif is_valid_src(netconf):
430 LOG.debug("applying network_config")
431 ns = net.parse_net_config(netconf)
432
433 if ns is not None:
434 net.render_network_state(target=target, network_state=ns)
435 else:390 else:
436 LOG.debug("copying interfaces")391 LOG.debug("copying interfaces")
437 copy_interfaces(interfaces, target)392 copy_interfaces(interfaces, target)
438393
439 _maybe_remove_legacy_eth0(target)
440
441394
442def copy_interfaces(interfaces, target):395def copy_interfaces(interfaces, target):
443 if not interfaces:396 if not interfaces:
444397
=== modified file 'curtin/net/__init__.py'
--- curtin/net/__init__.py 2016-06-16 17:43:17 +0000
+++ curtin/net/__init__.py 2016-08-29 20:01:45 +0000
@@ -299,7 +299,7 @@
299 mac = iface.get('mac_address', '')299 mac = iface.get('mac_address', '')
300 # len(macaddr) == 2 * 6 + 5 == 17300 # len(macaddr) == 2 * 6 + 5 == 17
301 if ifname and mac and len(mac) == 17:301 if ifname and mac and len(mac) == 17:
302 content += generate_udev_rule(ifname, mac)302 content += generate_udev_rule(ifname, mac.lower())
303303
304 return content304 return content
305305
@@ -349,7 +349,7 @@
349 'subnets',349 'subnets',
350 'type',350 'type',
351 ]351 ]
352 if iface['type'] not in ['bond', 'bridge']:352 if iface['type'] not in ['bond', 'bridge', 'vlan']:
353 ignore_map.append('mac_address')353 ignore_map.append('mac_address')
354354
355 for key, value in iface.items():355 for key, value in iface.items():
@@ -361,26 +361,52 @@
361 return content361 return content
362362
363363
364def render_route(route):364def render_route(route, indent=""):
365 content = "up route add"365 """When rendering routes for an iface, in some cases applying a route
366 may result in the route command returning non-zero which produces
367 some confusing output for users manually using ifup/ifdown[1]. To
368 that end, we will optionally include an '|| true' postfix to each
369 route line allowing users to work with ifup/ifdown without using
370 --force option.
371
372 We may at somepoint not want to emit this additional postfix, and
373 add a 'strict' flag to this function. When called with strict=True,
374 then we will not append the postfix.
375
376 1. http://askubuntu.com/questions/168033/
377 how-to-set-static-routes-in-ubuntu-server
378 """
379 content = []
380 up = indent + "post-up route add"
381 down = indent + "pre-down route del"
382 or_true = " || true"
366 mapping = {383 mapping = {
367 'network': '-net',384 'network': '-net',
368 'netmask': 'netmask',385 'netmask': 'netmask',
369 'gateway': 'gw',386 'gateway': 'gw',
370 'metric': 'metric',387 'metric': 'metric',
371 }388 }
372 for k in ['network', 'netmask', 'gateway', 'metric']:389 if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0':
373 if k in route:390 default_gw = " default gw %s" % route['gateway']
374 content += " %s %s" % (mapping[k], route[k])391 content.append(up + default_gw + or_true)
375392 content.append(down + default_gw + or_true)
376 content += '\n'393 elif route['network'] == '::' and route['netmask'] == 0:
377 return content394 # ipv6!
378395 default_gw = " -A inet6 default gw %s" % route['gateway']
379396 content.append(up + default_gw + or_true)
380def iface_start_entry(iface, index):397 content.append(down + default_gw + or_true)
398 else:
399 route_line = ""
400 for k in ['network', 'netmask', 'gateway', 'metric']:
401 if k in route:
402 route_line += " %s %s" % (mapping[k], route[k])
403 content.append(up + route_line + or_true)
404 content.append(down + route_line + or_true)
405 return "\n".join(content)
406
407
408def iface_start_entry(iface):
381 fullname = iface['name']409 fullname = iface['name']
382 if index != 0:
383 fullname += ":%s" % index
384410
385 control = iface['control']411 control = iface['control']
386 if control == "auto":412 if control == "auto":
@@ -397,6 +423,16 @@
397 "iface {fullname} {inet} {mode}\n").format(**subst)423 "iface {fullname} {inet} {mode}\n").format(**subst)
398424
399425
426def subnet_is_ipv6(subnet):
427 # 'static6' or 'dhcp6'
428 if subnet['type'].endswith('6'):
429 # This is a request for DHCPv6.
430 return True
431 elif subnet['type'] == 'static' and ":" in subnet['address']:
432 return True
433 return False
434
435
400def render_interfaces(network_state):436def render_interfaces(network_state):
401 ''' Given state, emit etc/network/interfaces content '''437 ''' Given state, emit etc/network/interfaces content '''
402438
@@ -424,42 +460,43 @@
424 content += "\n"460 content += "\n"
425 subnets = iface.get('subnets', {})461 subnets = iface.get('subnets', {})
426 if subnets:462 if subnets:
427 for index, subnet in zip(range(0, len(subnets)), subnets):463 for index, subnet in enumerate(subnets):
428 if content[-2:] != "\n\n":464 if content[-2:] != "\n\n":
429 content += "\n"465 content += "\n"
430 iface['index'] = index466 iface['index'] = index
431 iface['mode'] = subnet['type']467 iface['mode'] = subnet['type']
432 iface['control'] = subnet.get('control', 'auto')468 iface['control'] = subnet.get('control', 'auto')
433 subnet_inet = 'inet'469 subnet_inet = 'inet'
434 if iface['mode'].endswith('6'):470 if subnet_is_ipv6(subnet):
435 # This is a request for DHCPv6.
436 subnet_inet += '6'
437 elif iface['mode'] == 'static' and ":" in subnet['address']:
438 # This is a static IPv6 address.
439 subnet_inet += '6'471 subnet_inet += '6'
440 iface['inet'] = subnet_inet472 iface['inet'] = subnet_inet
441 if iface['mode'].startswith('dhcp'):473 if subnet['type'].startswith('dhcp'):
442 iface['mode'] = 'dhcp'474 iface['mode'] = 'dhcp'
443475
444 content += iface_start_entry(iface, index)476 # do not emit multiple 'auto $IFACE' lines as older (precise)
477 # ifupdown complains
478 if "auto %s\n" % (iface['name']) in content:
479 iface['control'] = 'alias'
480
481 content += iface_start_entry(iface)
445 content += iface_add_subnet(iface, subnet)482 content += iface_add_subnet(iface, subnet)
446 content += iface_add_attrs(iface, index)483 content += iface_add_attrs(iface, index)
447 if len(subnets) > 1 and index == 0:484
448 for i in range(1, len(subnets)):485 for route in subnet.get('routes', []):
449 content += " post-up ifup %s:%s\n" % (iface['name'],486 content += render_route(route, indent=" ") + '\n'
450 i)487
451 else:488 else:
452 # ifenslave docs say to auto the slave devices489 # ifenslave docs say to auto the slave devices
453 if 'bond-master' in iface:490 if 'bond-master' in iface or 'bond-slaves' in iface:
454 content += "auto {name}\n".format(**iface)491 content += "auto {name}\n".format(**iface)
455 content += "iface {name} {inet} {mode}\n".format(**iface)492 content += "iface {name} {inet} {mode}\n".format(**iface)
456 content += iface_add_attrs(iface, index)493 content += iface_add_attrs(iface, 0)
457494
458 for route in network_state.get('routes'):495 for route in network_state.get('routes'):
459 content += render_route(route)496 content += render_route(route)
460497
461 # global replacements until v2 format498 # global replacements until v2 format
462 content = content.replace('mac_address', 'hwaddress')499 content = content.replace('mac_address', 'hwaddress ether')
463500
464 # Play nice with others and source eni config files501 # Play nice with others and source eni config files
465 content += "\nsource /etc/network/interfaces.d/*.cfg\n"502 content += "\nsource /etc/network/interfaces.d/*.cfg\n"
466503
=== modified file 'curtin/net/network_state.py'
--- curtin/net/network_state.py 2015-09-18 18:12:02 +0000
+++ curtin/net/network_state.py 2016-08-29 20:01:45 +0000
@@ -121,6 +121,18 @@
121 iface = interfaces.get(command['name'], {})121 iface = interfaces.get(command['name'], {})
122 for param, val in command.get('params', {}).items():122 for param, val in command.get('params', {}).items():
123 iface.update({param: val})123 iface.update({param: val})
124
125 # convert subnet ipv6 netmask to cidr as needed
126 subnets = command.get('subnets')
127 if subnets:
128 for subnet in subnets:
129 if subnet['type'] == 'static':
130 if 'netmask' in subnet and ':' in subnet['address']:
131 subnet['netmask'] = mask2cidr(subnet['netmask'])
132 for route in subnet.get('routes', []):
133 if 'netmask' in route:
134 route['netmask'] = mask2cidr(route['netmask'])
135
124 iface.update({136 iface.update({
125 'name': command.get('name'),137 'name': command.get('name'),
126 'type': command.get('type'),138 'type': command.get('type'),
@@ -130,7 +142,7 @@
130 'mtu': command.get('mtu'),142 'mtu': command.get('mtu'),
131 'address': None,143 'address': None,
132 'gateway': None,144 'gateway': None,
133 'subnets': command.get('subnets'),145 'subnets': subnets,
134 })146 })
135 self.network_state['interfaces'].update({command.get('name'): iface})147 self.network_state['interfaces'].update({command.get('name'): iface})
136 self.dump_network_state()148 self.dump_network_state()
@@ -141,6 +153,7 @@
141 iface eth0.222 inet static153 iface eth0.222 inet static
142 address 10.10.10.1154 address 10.10.10.1
143 netmask 255.255.255.0155 netmask 255.255.255.0
156 hwaddress ether BC:76:4E:06:96:B3
144 vlan-raw-device eth0157 vlan-raw-device eth0
145 '''158 '''
146 required_keys = [159 required_keys = [
@@ -332,6 +345,37 @@
332 return ".".join([str(x) for x in mask])345 return ".".join([str(x) for x in mask])
333346
334347
348def ipv4mask2cidr(mask):
349 if '.' not in mask:
350 return mask
351 return sum([bin(int(x)).count('1') for x in mask.split('.')])
352
353
354def ipv6mask2cidr(mask):
355 if ':' not in mask:
356 return mask
357
358 bitCount = [0, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00,
359 0xff00, 0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc,
360 0xfffe, 0xffff]
361 cidr = 0
362 for word in mask.split(':'):
363 if not word or int(word, 16) == 0:
364 break
365 cidr += bitCount.index(int(word, 16))
366
367 return cidr
368
369
370def mask2cidr(mask):
371 if ':' in mask:
372 return ipv6mask2cidr(mask)
373 elif '.' in mask:
374 return ipv4mask2cidr(mask)
375 else:
376 return mask
377
378
335if __name__ == '__main__':379if __name__ == '__main__':
336 import sys380 import sys
337 import random381 import random
338382
=== added file 'examples/network-ipv6-bond-vlan.yaml'
--- examples/network-ipv6-bond-vlan.yaml 1970-01-01 00:00:00 +0000
+++ examples/network-ipv6-bond-vlan.yaml 2016-08-29 20:01:45 +0000
@@ -0,0 +1,56 @@
1network:
2 version: 1
3 config:
4 - name: interface0
5 type: physical
6 mac_address: BC:76:4E:06:96:B3
7 - name: interface1
8 type: physical
9 mac_address: BC:76:4E:04:88:41
10 - type: bond
11 bond_interfaces:
12 - interface0
13 - interface1
14 name: bond0
15 params:
16 bond_miimon: 100
17 bond_mode: 802.3ad
18 bond_xmit_hash_policy: layer3+4
19 - type: vlan
20 name: bond0.108
21 vlan_id: '108'
22 vlan_link: bond0
23 subnets:
24 - type: static
25 address: 65.61.151.38
26 netmask: 255.255.255.252
27 routes:
28 - gateway: 65.61.151.37
29 netmask: 0.0.0.0
30 network: 0.0.0.0
31 - type: static
32 address: 2001:4800:78ff:1b:be76:4eff:fe06:96b3
33 netmask: 'ffff:ffff:ffff:ffff::'
34 routes:
35 - gateway: 2001:4800:78ff:1b::1
36 netmask: '::'
37 network: '::'
38 - type: vlan
39 name: bond0.208
40 vlan_id: '208'
41 vlan_link: bond0
42 subnets:
43 - address: 10.184.225.122
44 netmask: 255.255.255.252
45 type: static
46 routes:
47 - gateway: 10.184.225.121
48 netmask: 255.240.0.0
49 network: 10.176.0.0
50 - gateway: 10.184.225.121
51 netmask: 255.240.0.0
52 network: 10.208.0.0
53 - type: nameserver
54 address: 72.3.128.240
55 - type: nameserver
56 address: 72.3.128.241
057
=== added file 'examples/tests/basic_network_static_ipv6.yaml'
--- examples/tests/basic_network_static_ipv6.yaml 1970-01-01 00:00:00 +0000
+++ examples/tests/basic_network_static_ipv6.yaml 2016-08-29 20:01:45 +0000
@@ -0,0 +1,22 @@
1showtrace: true
2network:
3 version: 1
4 config:
5 # Physical interfaces.
6 - type: physical
7 name: interface0
8 mac_address: "52:54:00:12:34:00"
9 subnets:
10 - type: static
11 address: 2001:4800:78ff:1b:be76:4eff:fe06:96b3
12 netmask: 'ffff:ffff:ffff:ffff::'
13 routes:
14 - gateway: 2001:4800:78ff:1b::1
15 netmask: '::'
16 network: '::'
17 - type: nameserver
18 address:
19 - 10.0.2.3
20 search:
21 - wark.maas
22 - foobar.maas
023
=== added file 'examples/tests/network_alias.yaml'
--- examples/tests/network_alias.yaml 1970-01-01 00:00:00 +0000
+++ examples/tests/network_alias.yaml 2016-08-29 20:01:45 +0000
@@ -0,0 +1,125 @@
1showtrace: true
2network:
3 version: 1
4 config:
5 # no-alias: single v4 and v6 on same interface
6 - type: physical
7 name: interface0
8 mac_address: "52:54:00:12:34:00"
9 subnets:
10 - type: static
11 address: 192.168.1.2/24
12 mtu: 1501
13 - type: static
14 address: 2001:4800:78ff:1b:be76:4eff:fe06:ffac
15 netmask: 'ffff:ffff:ffff:ffff::'
16 mtu: 1480
17 # multi_v4_alias: multiple v4 addrs on same interface
18 - type: physical
19 name: interface1
20 mac_address: "52:54:00:12:34:02"
21 subnets:
22 - type: static
23 address: 192.168.2.2/22
24 routes:
25 - network: 192.168.0.0
26 netmask: 255.255.252.0
27 gateway: 192.168.2.1
28 - type: static
29 address: 10.23.23.7/23
30 routes:
31 - gateway: 10.23.23.1
32 netmask: 255.255.254.0
33 network: 10.23.22.0
34 # multi_v6_alias: multiple v6 addrs on same interface
35 - type: physical
36 name: interface2
37 mac_address: "52:54:00:12:34:04"
38 subnets:
39 - type: static
40 address: 2001:4800:78ff:1b:be76:4eff:dead:1000
41 netmask: 'ffff:ffff:ffff:ffff::'
42 - type: static
43 address: 2001:4800:78ff:1b:be76:4eff:dead:2000
44 netmask: 'ffff:ffff:ffff:ffff::'
45 - type: static
46 address: 2001:4800:78ff:1b:be76:4eff:dead:3000
47 netmask: 'ffff:ffff:ffff:ffff::'
48 # multi_v4_and_v6_alias: multiple v4 and v6 addrs on same interface
49 - type: physical
50 name: interface3
51 mac_address: "52:54:00:12:34:06"
52 subnets:
53 - type: static
54 address: 192.168.7.7/22
55 routes:
56 - network: 192.168.0.0
57 netmask: 255.255.252.0
58 gateway: 192.168.7.1
59 - type: static
60 address: 10.99.99.23/23
61 routes:
62 - gateway: 10.99.99.1
63 netmask: 255.255.254.0
64 network: 10.99.98.0
65 - type: static
66 address: 2001:4800:78ff:1b:be76:4eff:beef:4000
67 netmask: 'ffff:ffff:ffff:ffff::'
68 - type: static
69 address: 2001:4800:78ff:1b:be76:4eff:beef:5000
70 netmask: 'ffff:ffff:ffff:ffff::'
71 - type: static
72 address: 2001:4800:78ff:1b:be76:4eff:beef:6000
73 netmask: 'ffff:ffff:ffff:ffff::'
74 # multi_v6_and_v4_revorder_alias: multiple v4 and v6 addr, rev order
75 - type: physical
76 name: interface4
77 mac_address: "52:54:00:12:34:08"
78 subnets:
79 - type: static
80 address: 2001:4800:78ff:1b:be76:4eff:debe:7000
81 netmask: 'ffff:ffff:ffff:ffff::'
82 - type: static
83 address: 2001:4800:78ff:1b:be76:4eff:debe:8000
84 netmask: 'ffff:ffff:ffff:ffff::'
85 - type: static
86 address: 2001:4800:78ff:1b:be76:4eff:debe:9000
87 netmask: 'ffff:ffff:ffff:ffff::'
88 - type: static
89 address: 192.168.100.100/22
90 routes:
91 - network: 192.168.0.0
92 netmask: 255.255.252.0
93 gateway: 192.168.100.1
94 - type: static
95 address: 10.17.142.2/23
96 routes:
97 - gateway: 10.17.142.1
98 netmask: 255.255.254.0
99 network: 10.17.142.0
100 # multi_v6_and_v4_mix_order: multiple v4 and v6 addr, mixed order
101 - type: physical
102 name: interface5
103 mac_address: "52:54:00:12:34:0a"
104 subnets:
105 - type: static
106 address: 2001:4800:78ff:1b:be76:4eff:baaf:a000
107 netmask: 'ffff:ffff:ffff:ffff::'
108 - type: static
109 address: 2001:4800:78ff:1b:be76:4eff:baaf:c000
110 netmask: 'ffff:ffff:ffff:ffff::'
111 - type: static
112 address: 192.168.200.200/22
113 routes:
114 - network: 192.168.0.0
115 netmask: 255.255.252.0
116 gateway: 192.168.200.1
117 - type: static
118 address: 10.252.2.2/23
119 routes:
120 - gateway: 10.252.2.1
121 netmask: 255.255.254.0
122 network: 10.252.2.0
123 - type: static
124 address: 2001:4800:78ff:1b:be76:4eff:baaf:b000
125 netmask: 'ffff:ffff:ffff:ffff::'
0126
=== added file 'examples/tests/network_mtu.yaml'
--- examples/tests/network_mtu.yaml 1970-01-01 00:00:00 +0000
+++ examples/tests/network_mtu.yaml 2016-08-29 20:01:45 +0000
@@ -0,0 +1,88 @@
1showtrace: true
2network:
3 version: 1
4 config:
5 - type: physical
6 name: interface0
7 mac_address: "52:54:00:12:34:00"
8 subnets:
9 - type: static
10 address: 192.168.1.2/24
11 mtu: 1501
12 - type: static
13 address: 2001:4800:78ff:1b:be76:4eff:fe06:1000
14 netmask: 'ffff:ffff:ffff:ffff::'
15 mtu: 1480
16 - type: physical
17 name: interface1
18 mac_address: "52:54:00:12:34:02"
19 subnets:
20 - type: static
21 address: 192.168.2.2/24
22 mtu: 1501
23 - type: static
24 address: 2001:4800:78ff:1b:be76:4eff:fe06:2000
25 netmask: 'ffff:ffff:ffff:ffff::'
26 mtu: 1501
27 - type: physical
28 name: interface2
29 mac_address: "52:54:00:12:34:04"
30 subnets:
31 - type: static
32 address: 192.168.3.2/24
33 - type: static
34 address: 2001:4800:78ff:1b:be76:4eff:fe06:3000
35 netmask: 'ffff:ffff:ffff:ffff::'
36 mtu: 1501
37 - type: physical
38 name: interface3
39 mac_address: "52:54:00:12:34:06"
40 subnets:
41 - type: manual
42 control: manual
43 - type: static
44 address: 2001:4800:78ff:1b:be76:4eff:fe06:4000
45 netmask: 'ffff:ffff:ffff:ffff::'
46 mtu: 9000
47 - type: physical
48 name: interface4
49 mac_address: "52:54:00:12:34:08"
50 subnets:
51 - type: static
52 address: 2001:4800:78ff:1b:be76:4eff:fe06:5000
53 netmask: 'ffff:ffff:ffff:ffff::'
54 mtu: 1480
55 - type: static
56 address: 192.168.5.2/24
57 mtu: 1501
58 - type: physical
59 name: interface5
60 mac_address: "52:54:00:12:34:0c"
61 subnets:
62 - type: static
63 address: 2001:4800:78ff:1b:be76:4eff:fe06:6000
64 netmask: 'ffff:ffff:ffff:ffff::'
65 mtu: 1501
66 - type: static
67 address: 192.168.6.2/24
68 mtu: 1501
69 - type: physical
70 name: interface6
71 mac_address: "52:54:00:12:34:0e"
72 subnets:
73 - type: static
74 address: 2001:4800:78ff:1b:be76:4eff:fe06:7000
75 netmask: 'ffff:ffff:ffff:ffff::'
76 mtu: 1501
77 - type: static
78 address: 192.168.7.2/24
79 - type: physical
80 name: interface7
81 mac_address: "52:54:00:12:35:01"
82 subnets:
83 - type: static
84 address: 2001:4800:78ff:1b:be76:4eff:fe06:8000
85 netmask: 'ffff:ffff:ffff:ffff::'
86 mtu: 9000
87 - type: manual
88 control: manual
089
=== added file 'examples/tests/network_source_ipv6.yaml'
--- examples/tests/network_source_ipv6.yaml 1970-01-01 00:00:00 +0000
+++ examples/tests/network_source_ipv6.yaml 2016-08-29 20:01:45 +0000
@@ -0,0 +1,31 @@
1showtrace: true
2network:
3 version: 1
4 config:
5 # Physical interfaces.
6 - type: physical
7 name: interface0
8 mac_address: "52:54:00:12:34:00"
9 subnets:
10 - type: static
11 address: 2001:4800:78ff:1b:be76:4eff:fe06:96b3
12 netmask: 'ffff:ffff:ffff:ffff::'
13 routes:
14 - gateway: 2001:4800:78ff:1b::1
15 netmask: '::'
16 network: '::'
17 - type: physical
18 name: interface2
19 mac_address: "52:54:00:12:34:04"
20 - type: nameserver
21 address:
22 - 10.0.2.3
23 search:
24 - wark.maas
25 - foobar.maas
26
27curthooks_commands:
28 # use curtin to inject a eni config file outside of the network yaml
29 # this allows us to test user installed configurations outside of
30 # curtin's control
31 aa_cleanup: ['curtin', 'in-target', '--', 'sh', '-c', "rm -f /etc/network/interfaces.d/eth0.cfg; /bin/echo -e 'auto interface2\niface interface2 inet static\n address 192.168.23.23/24\n' > /etc/network/interfaces.d/interface2.cfg"]
032
=== added file 'examples/tests/vlan_network_ipv6.yaml'
--- examples/tests/vlan_network_ipv6.yaml 1970-01-01 00:00:00 +0000
+++ examples/tests/vlan_network_ipv6.yaml 2016-08-29 20:01:45 +0000
@@ -0,0 +1,92 @@
1network:
2 config:
3 - id: interface0
4 mac_address: d4:be:d9:a8:49:13
5 mtu: 1500
6 name: interface0
7 subnets:
8 - address: 2001:4800:78ff:1b:be76:4eff:fe06:96b3
9 netmask: 'ffff:ffff:ffff:ffff::'
10 dns_nameservers:
11 - 10.245.168.2
12 routes:
13 - gateway: 2001:4800:78ff:1b::1
14 netmask: '::'
15 network: '::'
16 type: static
17 type: physical
18 - id: interface1
19 mac_address: d4:be:d9:a8:49:15
20 mtu: 1500
21 name: interface1
22 subnets:
23 - address: 2001:4800:beef:1b:be76:4eff:fe06:97b0
24 netmask: 'ffff:ffff:ffff:ffff::'
25 dns_nameservers: []
26 type: static
27 type: physical
28 - id: interface2
29 mac_address: d4:be:d9:a8:49:17
30 mtu: 1500
31 name: interface2
32 subnets:
33 - type: manual
34 control: manual
35 type: physical
36 - id: interface3
37 mac_address: d4:be:d9:a8:49:19
38 mtu: 1500
39 name: interface3
40 subnets:
41 - type: manual
42 control: manual
43 type: physical
44 - id: interface1.2667
45 mtu: 1500
46 name: interface1.2667
47 subnets:
48 - address: 2001:4800:dead:1b:be76:4eff:c486:12f7
49 netmask: 'ffff:ffff:ffff:ffff::'
50 dns_nameservers: []
51 type: static
52 type: vlan
53 vlan_id: 2667
54 vlan_link: interface1
55 - id: interface1.2668
56 mtu: 1500
57 name: interface1.2668
58 subnets:
59 - address: 2001:4800:feef:1b:be76:4eff:4242:2323
60 netmask: 'ffff:ffff:ffff:ffff::'
61 dns_nameservers: []
62 type: static
63 type: vlan
64 vlan_id: 2668
65 vlan_link: interface1
66 - id: interface1.2669
67 mtu: 1500
68 name: interface1.2669
69 subnets:
70 - address: 2001:4800:effe:1b:be76:7634:5f42:79ff
71 netmask: 'ffff:ffff:ffff:ffff::'
72 dns_nameservers: []
73 type: static
74 type: vlan
75 vlan_id: 2669
76 vlan_link: interface1
77 - id: interface1.2670
78 mtu: 1500
79 name: interface1.2670
80 subnets:
81 - address: 2001:4800:9eaf:1b:be76:7634:321f:bbca
82 netmask: 'ffff:ffff:ffff:ffff::'
83 dns_nameservers: []
84 type: static
85 type: vlan
86 vlan_id: 2670
87 vlan_link: interface1
88 - address: 10.245.168.2
89 search:
90 - dellstack
91 type: nameserver
92 version: 1
093
=== modified file 'tests/unittests/test_net.py'
--- tests/unittests/test_net.py 2016-06-16 17:43:17 +0000
+++ tests/unittests/test_net.py 2016-08-29 20:01:45 +0000
@@ -473,10 +473,9 @@
473473
474 auto eth0474 auto eth0
475 iface eth0 inet dhcp475 iface eth0 inet dhcp
476 post-up ifup eth0:1
477476
478 auto eth0:1477 # control-alias eth0
479 iface eth0:1 inet static478 iface eth0 inet static
480 address 192.168.21.3/24479 address 192.168.21.3/24
481 dns-nameservers 8.8.8.8 8.8.4.4480 dns-nameservers 8.8.8.8 8.8.4.4
482 dns-search barley.maas sach.maas481 dns-search barley.maas sach.maas
@@ -515,12 +514,11 @@
515 iface bond0 inet static514 iface bond0 inet static
516 address 10.23.23.2/24515 address 10.23.23.2/24
517 bond-mode active-backup516 bond-mode active-backup
518 hwaddress 52:54:00:12:34:06517 hwaddress ether 52:54:00:12:34:06
519 bond-slaves none518 bond-slaves none
520 post-up ifup bond0:1
521519
522 auto bond0:1520 # control-alias bond0
523 iface bond0:1 inet static521 iface bond0 inet static
524 address 10.23.24.2/24522 address 10.23.24.2/24
525523
526 source /etc/network/interfaces.d/*.cfg524 source /etc/network/interfaces.d/*.cfg
@@ -551,10 +549,9 @@
551 address 192.168.14.2/24549 address 192.168.14.2/24
552 gateway 192.168.14.1550 gateway 192.168.14.1
553 mtu 1492551 mtu 1492
554 post-up ifup interface1:1
555552
556 auto interface1:1553 # control-alias interface1
557 iface interface1:1 inet static554 iface interface1 inet static
558 address 192.168.14.4/24555 address 192.168.14.4/24
559556
560 allow-hotplug interface2557 allow-hotplug interface2
@@ -597,10 +594,9 @@
597 auto eth0594 auto eth0
598 iface eth0 inet6 static595 iface eth0 inet6 static
599 address fde9:8f83:4a81:1:0:1:0:6/64596 address fde9:8f83:4a81:1:0:1:0:6/64
600 post-up ifup eth0:1
601597
602 auto eth0:1598 # control-alias eth0
603 iface eth0:1 inet static599 iface eth0 inet static
604 address 192.168.0.1/24600 address 192.168.0.1/24
605601
606 source /etc/network/interfaces.d/*.cfg602 source /etc/network/interfaces.d/*.cfg
@@ -613,5 +609,50 @@
613 self.assertEqual(sorted(ifaces.split('\n')),609 self.assertEqual(sorted(ifaces.split('\n')),
614 sorted(net_ifaces.split('\n')))610 sorted(net_ifaces.split('\n')))
615611
612 def test_render_interfaces_ipv4_multiple_alias(self):
613 network_yaml = '''
614network:
615 version: 1
616 config:
617 # multi_v4_alias: multiple v4 addrs on same interface
618 - type: physical
619 name: interface1
620 mac_address: "52:54:00:12:34:02"
621 subnets:
622 - type: dhcp
623 - type: static
624 address: 192.168.2.2/22
625 gateway: 192.168.2.1
626 - type: static
627 address: 10.23.23.7/23
628 gateway: 10.23.23.1
629'''
630
631 ns = self.get_net_state(network_yaml)
632 ifaces = dedent("""\
633 auto lo
634 iface lo inet loopback
635
636 auto interface1
637 iface interface1 inet dhcp
638
639 # control-alias interface1
640 iface interface1 inet static
641 address 192.168.2.2/22
642 gateway 192.168.2.1
643
644 # control-alias interface1
645 iface interface1 inet static
646 address 10.23.23.7/23
647 gateway 10.23.23.1
648
649 source /etc/network/interfaces.d/*.cfg
650 """)
651 net_ifaces = net.render_interfaces(ns.network_state)
652 print(net_ifaces)
653 print(ifaces)
654 self.assertEqual(sorted(ifaces.split('\n')),
655 sorted(net_ifaces.split('\n')))
656
616657
617# vi: ts=4 expandtab syntax=python658# vi: ts=4 expandtab syntax=python
618659
=== modified file 'tests/vmtests/helpers.py'
--- tests/vmtests/helpers.py 2016-06-16 18:45:05 +0000
+++ tests/vmtests/helpers.py 2016-08-29 20:01:45 +0000
@@ -119,170 +119,133 @@
119 return sorted(releases)119 return sorted(releases)
120120
121121
122def _parse_ifconfig_xenial(ifconfig_out):122def _parse_ip_a(ip_a):
123 """Parse ifconfig output from xenial or earlier and return a dictionary.123 """
124 given content like below, return:124 2: interface0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc pfifo_fast\
125 {'eth0': {'address': '10.8.1.78', 'broadcast': '10.8.1.255',125 state UP group default qlen 1000
126 'inet6': [{'address': 'fe80::216:3eff:fe63:c05d',126 link/ether 52:54:00:12:34:00 brd ff:ff:ff:ff:ff:ff
127 'prefixlen': '64', 'scope': 'Link'},127 inet 192.168.1.2/24 brd 192.168.1.255 scope global interface0
128 {'address': 'fdec:2922:2f07:0:216:3eff:fe63:c05d',128 valid_lft forever preferred_lft forever
129 'prefixlen': '64', 'scope': 'Global'}],129 inet6 2001:4800:78ff:1b:be76:4eff:fe06:1000/64 scope global
130 'interface': 'eth0', 'link_encap': 'Ethernet',130 valid_lft forever preferred_lft forever
131 'mac_address': '00:16:3e:63:c0:5d', 'mtu': 1500,131 inet6 fe80::5054:ff:fe12:3400/64 scope link
132 'multicast': True, 'netmask': '255.255.255.0',132 valid_lft forever preferred_lft forever
133 'running': True, 'up': True}}133 """
134134 ifaces = {}
135 eth0 Link encap:Ethernet HWaddr 00:16:3e:63:c0:5d135 combined_fields = {
136 inet addr:10.8.1.78 Bcast:10.8.1.255 Mask:255.255.255.0136 'brd': 'broadcast',
137 inet6 addr: fe80::216:3eff:fe63:c05d/64 Scope:Link137 'link/ether': 'mac_address',
138 inet6 addr: fdec:2922:2f07:0:216:3eff:fe63:c05d/64 Scope:Global138 }
139 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1139 interface_fields = [
140 RX packets:21503 errors:0 dropped:0 overruns:0 frame:0140 'group',
141 TX packets:11346 errors:0 dropped:0 overruns:0 carrier:0141 'master',
142 collisions:0 txqueuelen:1000142 'mtu',
143 RX bytes:31556357 (31.5 MB) TX bytes:870943 (870.9 KB)143 'qdisc',
144 """144 'qlen',
145 ifaces = {}145 'state',
146 combined_fields = {'addr': 'address', 'Bcast': 'broadcast',146 ]
147 'Mask': 'netmask', 'MTU': 'mtu',147 inet_fields = [
148 'encap': 'link_encap'}148 'valid_lft',
149 boolmap = {'RUNNING': 'running', 'UP': 'up', 'MULTICAST': 'multicast'}149 'preferred_left'
150150 ]
151 for line in ifconfig_out.splitlines():151 boolmap = {
152 if not line:152 'BROADCAST': 'broadcast',
153 continue153 'LOOPBACK': 'loopback',
154 if not line.startswith(" "):154 'LOWER_UP': 'lower_up',
155 cur_iface = line.split()[0].rstrip(":")155 'MULTICAST': 'multicast',
156 cur_data = {'inet6': [], 'interface': cur_iface}156 'RUNNING': 'running',
157 for t in boolmap.values():157 'UP': 'up',
158 cur_data[t] = False158 }
159 ifaces[cur_iface] = cur_data159
160160 for line in ip_a.splitlines():
161 toks = line.split()161 if not line:
162162 continue
163 if toks[0] == "inet6":163
164 cidr = toks[2]164 toks = line.split()
165 address, prefixlen = cidr.split("/")165 if not line.startswith(" "):
166 scope = toks[3].split(":")[1]166 cur_iface = line.split()[1].rstrip(":")
167 cur_ipv6 = {'address': address, 'scope': scope,167 cur_data = {
168 'prefixlen': prefixlen}168 'inet4': [],
169 cur_data['inet6'].append(cur_ipv6)169 'inet6': [],
170 continue170 'interface': cur_iface
171171 }
172 for i in range(0, len(toks)):172 # vlan's get a fancy name <iface name>@<vlan_link>
173 cur_tok = toks[i]173 if '@' in cur_iface:
174 try:174 cur_iface, vlan_link = cur_iface.split("@")
175 next_tok = toks[i+1]175 cur_data.update({'interface': cur_iface,
176 except IndexError:176 'vlan_link': vlan_link})
177 next_tok = None177 for t in boolmap.values():
178178 # <BROADCAST,MULTICAST,UP,LOWER_UP>
179 if cur_tok == "HWaddr":179 cur_data[t] = t.upper() in line[2]
180 cur_data['mac_address'] = next_tok180 ifaces[cur_iface] = cur_data
181 elif ":" in cur_tok:181
182 key, _colon, val = cur_tok.partition(":")182 for i in range(0, len(toks)):
183 if key in combined_fields:183 cur_tok = toks[i]
184 cur_data[combined_fields[key]] = val184 try:
185 elif cur_tok in boolmap:185 next_tok = toks[i+1]
186 cur_data[boolmap[cur_tok]] = True186 except IndexError:
187187 next_tok = None
188 if 'mtu' in cur_data:188
189 cur_data['mtu'] = int(cur_data['mtu'])189 # parse link/ether, brd
190190 if cur_tok in combined_fields.keys():
191 return ifaces191 cur_data[combined_fields[cur_tok]] = next_tok
192192 # mtu an other interface line key/value pairs
193193 elif cur_tok in interface_fields:
194def _parse_ifconfig_yakkety(ifconfig_out):194 cur_data[cur_tok] = next_tok
195 """Parse ifconfig output from yakkety or later(?) and return a dictionary.195 elif cur_tok.startswith("inet"):
196196 cidr = toks[1]
197 given ifconfig output like below, return:197 address, prefixlen = cidr.split("/")
198 {'ens2': {'address': '10.5.0.78',198 cur_ip = {
199 'broadcast': '10.5.255.255',199 'address': address,
200 'broadcast_flag': True,200 'prefixlen': prefixlen,
201 'inet6': [{'address': 'fe80::f816:3eff:fe05:9673',201 }
202 'prefixlen': '64', 'scopeid': '0x20<link>'},202 if ":" in address:
203 {'address': 'fe80::f816:3eff:fe05:9673',203 cur_ipv6 = cur_ip.copy()
204 'prefixlen': '64', 'scopeid': '0x20<link>'}],204 cur_ipv6.update({'scope': toks[3]})
205 'interface': 'ens2', 'link_encap': 'Ethernet',205 cur_data['inet6'].append(cur_ipv6)
206 'mac_address': 'fa:16:3e:05:96:73', 'mtu': 1500,206 else:
207 'multicast': True, 'netmask': '255.255.0.0',207 cur_ipv4 = cur_ip.copy()
208 'running': True, 'up': True}}208 if len(toks) > 5:
209209 cur_ipv4.update({'scope': toks[5]})
210 ens2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500210 else:
211 inet 10.5.0.78 netmask 255.255.0.0 broadcast 10.5.255.255211 cur_ipv4.update({'scope': toks[3]})
212 inet6 fe80::f816:3eff:fe05:9673 prefixlen 64 scopeid 0x20<link>212 cur_data['inet4'].append(cur_ipv4)
213 inet6 fe80::f816:3eff:fe05:9673 prefixlen 64 scopeid 0x20<link>213
214 ether fa:16:3e:05:96:73 txqueuelen 1000 (Ethernet)214 continue
215 RX packets 33196 bytes 48916947 (48.9 MB)215 elif cur_tok in inet_fields:
216 RX errors 0 dropped 0 overruns 0 frame 0216 if ":" in address:
217 TX packets 5458 bytes 411486 (411.4 KB)217 cur_ipv6[cur_tok] = next_tok
218 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0218 else:
219 """219 cur_ipv4[cur_tok] = next_tok
220 fmap = {'mtu': 'mtu', 'inet': 'address',220 continue
221 'netmask': 'netmask', 'broadcast': 'broadcast',221
222 'ether': 'mac_address'}222 return ifaces
223 boolmap = {'RUNNING': 'running', 'UP': 'up', 'MULTICAST': 'multicast',223
224 'BROADCAST': 'broadcast_flag'}224
225225def ip_a_to_dict(ip_a):
226 ifaces = {}
227 for line in ifconfig_out.splitlines():
228 if not line:
229 continue
230 if not line.startswith(" "):
231 cur_iface = line.split()[0].rstrip(":")
232 cur_data = {'inet6': [], 'interface': cur_iface}
233 for t in boolmap.values():
234 cur_data[t] = False
235 ifaces[cur_iface] = cur_data
236
237 toks = line.split()
238 if toks[0] == "inet6":
239 cur_ipv6 = {'address': toks[1]}
240 cur_data['inet6'].append(cur_ipv6)
241
242 for i in range(0, len(toks)):
243 cur_tok = toks[i]
244 try:
245 next_tok = toks[i+1]
246 except IndexError:
247 next_tok = None
248 if cur_tok in fmap:
249 cur_data[fmap[cur_tok]] = next_tok
250 elif cur_tok in ('prefixlen', 'scopeid'):
251 cur_ipv6[cur_tok] = next_tok
252 cur_data['inet6'].append
253 elif cur_tok.startswith("flags="):
254 # flags=4163<UP,BROADCAST,RUNNING,MULTICAST>
255 flags = cur_tok[cur_tok.find("<") + 1:
256 cur_tok.rfind(">")].split(",")
257 for flag in flags:
258 if flag in boolmap:
259 cur_data[boolmap[flag]] = True
260 elif cur_tok == "(Ethernet)":
261 cur_data['link_encap'] = 'Ethernet'
262
263 if 'mtu' in cur_data:
264 cur_data['mtu'] = int(cur_data['mtu'])
265
266 return ifaces
267
268
269def ifconfig_to_dict(ifconfig_a):
270 # if the first token of the first line ends in a ':' then assume yakkety
271 # parse ifconfig output and return a dictionary.
272 #
273 # return a dictionary of network information like:226 # return a dictionary of network information like:
274 # {'ens2': {'address': '10.5.0.78', 'broadcast': '10.5.255.255',227 # {'interface0': {'broadcast': '10.0.2.255',
275 # 'broadcast_flag': True,228 # 'group': 'default',
276 # 'inet6': [{'address': 'fe80::f816:3eff:fe05:9673',229 # 'inet4': [{'address': '10.0.2.15',
277 # 'prefixlen': '64', 'scopeid': '0x20<link>'},230 # 'preferred_lft': 'forever',
278 # {'address': 'fe80::f816:3eff:fe05:9673',231 # 'prefixlen': '24',
279 # 'prefixlen': '64', 'scopeid': '0x20<link>'}],232 # 'scope': 'global',
280 # 'interface': 'ens2', 'link_encap': 'Ethernet',233 # 'valid_lft': 'forever'}],
281 # 'mac_address': 'fa:16:3e:05:96:73', 'mtu': 1500,234 # 'inet6': [{'address': 'fe80::5054:ff:fe12:3400',
282 # 'multicast': True, 'netmask': '255.255.0.0',235 # 'preferred_lft': 'forever',
283 # 'running': True, 'up': True}}236 # 'prefixlen': '64',
284 line = ifconfig_a.lstrip().splitlines()[0]237 # 'scope': 'link',
285 if line.split()[0].endswith(":"):238 # 'valid_lft': 'forever'}],
286 return _parse_ifconfig_yakkety(ifconfig_a)239 # 'interface': 'interface0',
287 else:240 # 'loopback': False,
288 return _parse_ifconfig_xenial(ifconfig_a)241 # 'lower_up': False,
242 # 'mac_address': '52:54:00:12:34:00',
243 # 'mtu': '1500',
244 # 'multicast': False,
245 # 'qdisc': 'pfifo_fast',
246 # 'qlen': '1000',
247 # 'running': False,
248 # 'state': 'UP',
249 # 'up': False},
250 # from iproute2 `ip a` command output
251 return _parse_ip_a(ip_a)
289252
=== modified file 'tests/vmtests/test_basic.py'
--- tests/vmtests/test_basic.py 2016-08-09 15:01:07 +0000
+++ tests/vmtests/test_basic.py 2016-08-29 20:01:45 +0000
@@ -3,7 +3,6 @@
3 get_apt_proxy)3 get_apt_proxy)
4from .releases import base_vm_classes as relbase4from .releases import base_vm_classes as relbase
55
6import os
7import re6import re
8import textwrap7import textwrap
98
@@ -47,21 +46,17 @@
47 def test_partition_numbers(self):46 def test_partition_numbers(self):
48 # vde should have partitions 1 and 1047 # vde should have partitions 1 and 10
49 disk = "vde"48 disk = "vde"
50 proc_partitions_path = os.path.join(self.td.collect,
51 'proc_partitions')
52 self.assertTrue(os.path.exists(proc_partitions_path))
53 found = []49 found = []
54 with open(proc_partitions_path, 'r') as fp:50 proc_partitions = self.load_collect_file('proc_partitions')
55 for line in fp.readlines():51 for line in proc_partitions.splitlines():
56 if disk in line:52 if disk in line:
57 found.append(line.split()[3])53 found.append(line.split()[3])
58 # /proc/partitions should have 3 lines with 'vde' in them.54 # /proc/partitions should have 3 lines with 'vde' in them.
59 expected = [disk + s for s in ["", "1", "10"]]55 expected = [disk + s for s in ["", "1", "10"]]
60 self.assertEqual(found, expected)56 self.assertEqual(found, expected)
6157
62 def test_partitions(self):58 def test_partitions(self):
63 with open(os.path.join(self.td.collect, "fstab")) as fp:59 fstab_lines = self.load_collect_file('fstab').splitlines()
64 fstab_lines = fp.readlines()
65 print("\n".join(fstab_lines))60 print("\n".join(fstab_lines))
66 # Test that vda1 is on /61 # Test that vda1 is on /
67 blkid_info = self.get_blkid_data("blkid_output_vda1")62 blkid_info = self.get_blkid_data("blkid_output_vda1")
@@ -94,12 +89,8 @@
9489
95 def test_whole_disk_format(self):90 def test_whole_disk_format(self):
96 # confirm the whole disk format is the expected device91 # confirm the whole disk format is the expected device
97 with open(os.path.join(self.td.collect,92 btrfs_show_super = self.load_collect_file('btrfs_show_super_vdd')
98 "btrfs_show_super_vdd"), "r") as fp:93 ls_uuid = self.load_collect_file("ls_uuid")
99 btrfs_show_super = fp.read()
100
101 with open(os.path.join(self.td.collect, "ls_uuid"), "r") as fp:
102 ls_uuid = fp.read()
10394
104 # extract uuid from btrfs superblock95 # extract uuid from btrfs superblock
105 btrfs_fsid = [line for line in btrfs_show_super.split('\n')96 btrfs_fsid = [line for line in btrfs_show_super.split('\n')
@@ -123,8 +114,7 @@
123114
124 def test_proxy_set(self):115 def test_proxy_set(self):
125 expected = get_apt_proxy()116 expected = get_apt_proxy()
126 with open(os.path.join(self.td.collect, "apt-proxy")) as fp:117 apt_proxy_found = self.load_collect_file("apt-proxy").rstrip()
127 apt_proxy_found = fp.read().rstrip()
128 if expected:118 if expected:
129 # the proxy should have gotten set through119 # the proxy should have gotten set through
130 self.assertIn(expected, apt_proxy_found)120 self.assertIn(expected, apt_proxy_found)
@@ -157,12 +147,8 @@
157147
158 def test_whole_disk_format(self):148 def test_whole_disk_format(self):
159 # confirm the whole disk format is the expected device149 # confirm the whole disk format is the expected device
160 with open(os.path.join(self.td.collect,150 btrfs_show_super = self.load_collect_file("btrfs_show_super_vdd")
161 "btrfs_show_super_vdd"), "r") as fp:151 ls_uuid = self.load_collect_file("ls_uuid")
162 btrfs_show_super = fp.read()
163
164 with open(os.path.join(self.td.collect, "ls_uuid"), "r") as fp:
165 ls_uuid = fp.read()
166152
167 # extract uuid from btrfs superblock153 # extract uuid from btrfs superblock
168 btrfs_fsid = re.findall('.*uuid:\ (.*)\n', btrfs_show_super)154 btrfs_fsid = re.findall('.*uuid:\ (.*)\n', btrfs_show_super)
@@ -275,21 +261,17 @@
275 def test_partition_numbers(self):261 def test_partition_numbers(self):
276 # vde should have partitions 1 and 10262 # vde should have partitions 1 and 10
277 disk = "sdd"263 disk = "sdd"
278 proc_partitions_path = os.path.join(self.td.collect,
279 'proc_partitions')
280 self.assertTrue(os.path.exists(proc_partitions_path))
281 found = []264 found = []
282 with open(proc_partitions_path, 'r') as fp:265 proc_partitions = self.load_collect_file('proc_partitions')
283 for line in fp.readlines():266 for line in proc_partitions.splitlines():
284 if disk in line:267 if disk in line:
285 found.append(line.split()[3])268 found.append(line.split()[3])
286 # /proc/partitions should have 3 lines with 'vde' in them.269 # /proc/partitions should have 3 lines with 'vde' in them.
287 expected = [disk + s for s in ["", "1", "10"]]270 expected = [disk + s for s in ["", "1", "10"]]
288 self.assertEqual(found, expected)271 self.assertEqual(found, expected)
289272
290 def test_partitions(self):273 def test_partitions(self):
291 with open(os.path.join(self.td.collect, "fstab")) as fp:274 fstab_lines = self.load_collect_file('fstab').splitlines()
292 fstab_lines = fp.readlines()
293 print("\n".join(fstab_lines))275 print("\n".join(fstab_lines))
294 # Test that vda1 is on /276 # Test that vda1 is on /
295 blkid_info = self.get_blkid_data("blkid_output_sda1")277 blkid_info = self.get_blkid_data("blkid_output_sda1")
@@ -322,12 +304,8 @@
322304
323 def test_whole_disk_format(self):305 def test_whole_disk_format(self):
324 # confirm the whole disk format is the expected device306 # confirm the whole disk format is the expected device
325 with open(os.path.join(self.td.collect,307 btrfs_show_super = self.load_collect_file("btrfs_show_super_sdc")
326 "btrfs_show_super_sdc"), "r") as fp:308 ls_uuid = self.load_collect_file("ls_uuid")
327 btrfs_show_super = fp.read()
328
329 with open(os.path.join(self.td.collect, "ls_uuid"), "r") as fp:
330 ls_uuid = fp.read()
331309
332 # extract uuid from btrfs superblock310 # extract uuid from btrfs superblock
333 btrfs_fsid = [line for line in btrfs_show_super.split('\n')311 btrfs_fsid = [line for line in btrfs_show_super.split('\n')
334312
=== modified file 'tests/vmtests/test_bcache_basic.py'
--- tests/vmtests/test_bcache_basic.py 2016-06-13 20:49:15 +0000
+++ tests/vmtests/test_bcache_basic.py 2016-08-29 20:01:45 +0000
@@ -2,7 +2,6 @@
2from .releases import base_vm_classes as relbase2from .releases import base_vm_classes as relbase
33
4import textwrap4import textwrap
5import os
65
76
8class TestBcacheBasic(VMBaseClass):7class TestBcacheBasic(VMBaseClass):
@@ -27,14 +26,12 @@
2726
28 def test_bcache_status(self):27 def test_bcache_status(self):
29 bcache_cset_uuid = None28 bcache_cset_uuid = None
30 fname = os.path.join(self.td.collect, "bcache_super_vda2")29 for line in self.load_collect_file("bcache_super_vda2").splitlines():
31 with open(fname, "r") as fp:30 if line != "" and line.split()[0] == "cset.uuid":
32 for line in fp.read().splitlines():31 bcache_cset_uuid = line.split()[-1].rstrip()
33 if line != "" and line.split()[0] == "cset.uuid":
34 bcache_cset_uuid = line.split()[-1].rstrip()
35 self.assertIsNotNone(bcache_cset_uuid)32 self.assertIsNotNone(bcache_cset_uuid)
36 with open(os.path.join(self.td.collect, "bcache_ls"), "r") as fp:33 self.assertTrue(bcache_cset_uuid in
37 self.assertTrue(bcache_cset_uuid in fp.read().splitlines())34 self.load_collect_file("bcache_ls").splitlines())
3835
39 def test_bcache_cachemode(self):36 def test_bcache_cachemode(self):
40 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")37 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")
4138
=== removed file 'tests/vmtests/test_bonding.py'
--- tests/vmtests/test_bonding.py 2016-08-09 15:01:07 +0000
+++ tests/vmtests/test_bonding.py 1970-01-01 00:00:00 +0000
@@ -1,205 +0,0 @@
1from . import VMBaseClass, logger, helpers
2from .releases import base_vm_classes as relbase
3
4import ipaddress
5import os
6import re
7import textwrap
8import yaml
9
10
11class TestNetworkAbs(VMBaseClass):
12 interactive = False
13 conf_file = "examples/tests/bonding_network.yaml"
14 extra_disks = []
15 extra_nics = []
16 collect_scripts = [textwrap.dedent("""
17 cd OUTPUT_COLLECT_D
18 ifconfig -a > ifconfig_a
19 cp -av /etc/network/interfaces .
20 cp -av /etc/udev/rules.d/70-persistent-net.rules .
21 ip -o route show > ip_route_show
22 route -n > route_n
23 dpkg-query -W -f '${Status}' ifenslave > ifenslave_installed
24 find /etc/network/interfaces.d > find_interfacesd
25 """)]
26
27 def test_output_files_exist(self):
28 self.output_files_exist(["ifconfig_a",
29 "interfaces",
30 "70-persistent-net.rules",
31 "ip_route_show",
32 "ifenslave_installed",
33 "route_n"])
34
35 def test_ifenslave_installed(self):
36 with open(os.path.join(self.td.collect, "ifenslave_installed")) as fp:
37 status = fp.read().strip()
38 logger.debug('ifenslave installed: {}'.format(status))
39 self.assertEqual('install ok installed', status)
40
41 def test_etc_network_interfaces(self):
42 with open(os.path.join(self.td.collect, "interfaces")) as fp:
43 eni = fp.read()
44 logger.debug('etc/network/interfaces:\n{}'.format(eni))
45
46 expected_eni = self.get_expected_etc_network_interfaces()
47 eni_lines = eni.split('\n')
48 for line in expected_eni.split('\n'):
49 self.assertTrue(line in eni_lines)
50
51 def test_ifconfig_output(self):
52 '''check ifconfig output with test input'''
53 network_state = self.get_network_state()
54 logger.debug('expected_network_state:\n{}'.format(
55 yaml.dump(network_state, default_flow_style=False, indent=4)))
56
57 with open(os.path.join(self.td.collect, "ifconfig_a")) as fp:
58 ifconfig_a = fp.read()
59 logger.debug('ifconfig -a:\n{}'.format(ifconfig_a))
60
61 ifconfig_dict = helpers.ifconfig_to_dict(ifconfig_a)
62 logger.debug('parsed ifcfg dict:\n{}'.format(
63 yaml.dump(ifconfig_dict, default_flow_style=False, indent=4)))
64
65 with open(os.path.join(self.td.collect, "ip_route_show")) as fp:
66 ip_route_show = fp.read()
67 logger.debug("ip route show:\n{}".format(ip_route_show))
68 for line in [line for line in ip_route_show.split('\n')
69 if 'src' in line]:
70 m = re.search(r'^(?P<network>\S+)\sdev\s' +
71 r'(?P<devname>\S+)\s+' +
72 r'proto kernel\s+scope link' +
73 r'\s+src\s(?P<src_ip>\S+)',
74 line)
75 route_info = m.groupdict('')
76 logger.debug(route_info)
77
78 with open(os.path.join(self.td.collect, "route_n")) as fp:
79 route_n = fp.read()
80 logger.debug("route -n:\n{}".format(route_n))
81
82 interfaces = network_state.get('interfaces')
83 for iface in interfaces.values():
84 subnets = iface.get('subnets', {})
85 if subnets:
86 for index, subnet in zip(range(0, len(subnets)), subnets):
87 iface['index'] = index
88 if index == 0:
89 ifname = "{name}".format(**iface)
90 else:
91 ifname = "{name}:{index}".format(**iface)
92
93 self.check_interface(iface,
94 ifconfig_dict.get(ifname),
95 route_n)
96 else:
97 iface['index'] = 0
98 self.check_interface(iface,
99 ifconfig_dict.get(iface['name']),
100 route_n)
101
102 def check_interface(self, iface, ifconfig, route_n):
103 logger.debug(
104 'testing iface:\n{}\n\nifconfig:\n{}'.format(iface, ifconfig))
105 subnets = iface.get('subnets', {})
106 if subnets and iface['index'] != 0:
107 ifname = "{name}:{index}".format(**iface)
108 else:
109 ifname = "{name}".format(**iface)
110
111 # initial check, do we have the correct iface ?
112 logger.debug('ifname={}'.format(ifname))
113 logger.debug("ifconfig['interface']={}".format(ifconfig['interface']))
114 self.assertEqual(ifname, ifconfig['interface'])
115
116 # check physical interface attributes
117 # FIXME: can't check mac_addr under bonding since
118 # the bond might change slave mac addrs
119 for key in ['mtu']:
120 if key in iface and iface[key]:
121 self.assertEqual(iface[key],
122 ifconfig[key])
123
124 def __get_subnet(subnets, subidx):
125 for index, subnet in zip(range(0, len(subnets)), subnets):
126 if index == subidx:
127 break
128 return subnet
129
130 # check subnet related attributes, and specifically only
131 # the subnet specified by iface['index']
132 subnets = iface.get('subnets', {})
133 if subnets:
134 subnet = __get_subnet(subnets, iface['index'])
135 if 'address' in subnet and subnet['address']:
136 if ':' in subnet['address']:
137 inet_iface = ipaddress.IPv6Interface(
138 subnet['address'])
139 else:
140 inet_iface = ipaddress.IPv4Interface(
141 subnet['address'])
142
143 # check ip addr
144 self.assertEqual(str(inet_iface.ip),
145 ifconfig['address'])
146
147 self.assertEqual(str(inet_iface.netmask),
148 ifconfig['netmask'])
149
150 self.assertEqual(
151 str(inet_iface.network.broadcast_address),
152 ifconfig['broadcast'])
153
154 # handle gateway by looking at routing table
155 if 'gateway' in subnet and subnet['gateway']:
156 gw_ip = subnet['gateway']
157 gateways = [line for line in route_n.split('\n')
158 if 'UG' in line and gw_ip in line]
159 logger.debug('matching gateways:\n{}'.format(gateways))
160 self.assertEqual(len(gateways), 1)
161 [gateways] = gateways
162 (dest, gw, genmask, flags, metric, ref, use, iface) = \
163 gateways.split()
164 logger.debug('expected gw:{} found gw:{}'.format(gw_ip, gw))
165 self.assertEqual(gw_ip, gw)
166
167
168class PreciseHWETTestBonding(relbase.precise_hwe_t, TestNetworkAbs):
169 __test__ = True
170 # package names on precise are different, need to check on ifenslave-2.6
171 collect_scripts = TestNetworkAbs.collect_scripts + [textwrap.dedent("""
172 cd OUTPUT_COLLECT_D
173 dpkg-query -W -f '${Status}' ifenslave-2.6 > ifenslave_installed
174 """)]
175
176
177class TrustyTestBonding(relbase.trusty, TestNetworkAbs):
178 __test__ = False
179
180
181class TrustyHWEUTestBonding(relbase.trusty_hwe_u, TrustyTestBonding):
182 __test__ = True
183
184
185class TrustyHWEVTestBonding(relbase.trusty_hwe_v, TrustyTestBonding):
186 # Working, but off by default to safe test suite runtime
187 # oldest/newest HWE-* covered above/below
188 __test__ = False
189
190
191class TrustyHWEWTestBonding(relbase.trusty_hwe_w, TrustyTestBonding):
192 __test__ = True
193
194
195class WilyTestBonding(relbase.wily, TestNetworkAbs):
196 # EOL - 2016-07-28
197 __test__ = False
198
199
200class XenialTestBonding(relbase.xenial, TestNetworkAbs):
201 __test__ = True
202
203
204class YakketyTestBonding(relbase.yakkety, TestNetworkAbs):
205 __test__ = True
2060
=== modified file 'tests/vmtests/test_mdadm_bcache.py'
--- tests/vmtests/test_mdadm_bcache.py 2016-08-09 15:01:07 +0000
+++ tests/vmtests/test_mdadm_bcache.py 2016-08-29 20:01:45 +0000
@@ -2,7 +2,6 @@
2from .releases import base_vm_classes as relbase2from .releases import base_vm_classes as relbase
33
4import textwrap4import textwrap
5import os
65
76
8class TestMdadmAbs(VMBaseClass):7class TestMdadmAbs(VMBaseClass):
@@ -82,17 +81,16 @@
82 bcache_cset_uuid = None81 bcache_cset_uuid = None
83 found = {}82 found = {}
84 for bcache_super in bcache_supers:83 for bcache_super in bcache_supers:
85 with open(os.path.join(self.td.collect, bcache_super), "r") as fp:84 for line in self.load_collect_file(bcache_super).splitlines():
86 for line in fp.read().splitlines():85 if line != "" and line.split()[0] == "cset.uuid":
87 if line != "" and line.split()[0] == "cset.uuid":86 bcache_cset_uuid = line.split()[-1].rstrip()
88 bcache_cset_uuid = line.split()[-1].rstrip()87 if bcache_cset_uuid in found:
89 if bcache_cset_uuid in found:88 found[bcache_cset_uuid].append(bcache_super)
90 found[bcache_cset_uuid].append(bcache_super)89 else:
91 else:90 found[bcache_cset_uuid] = [bcache_super]
92 found[bcache_cset_uuid] = [bcache_super]
93 self.assertIsNotNone(bcache_cset_uuid)91 self.assertIsNotNone(bcache_cset_uuid)
94 with open(os.path.join(self.td.collect, "bcache_ls"), "r") as fp:92 self.assertTrue(bcache_cset_uuid in
95 self.assertTrue(bcache_cset_uuid in fp.read().splitlines())93 self.load_collect_file("bcache_ls").splitlines())
9694
97 # one cset.uuid for all devices95 # one cset.uuid for all devices
98 self.assertEqual(len(found), 1)96 self.assertEqual(len(found), 1)
9997
=== modified file 'tests/vmtests/test_multipath.py'
--- tests/vmtests/test_multipath.py 2016-06-23 14:26:39 +0000
+++ tests/vmtests/test_multipath.py 2016-08-29 20:01:45 +0000
@@ -1,7 +1,6 @@
1from . import VMBaseClass1from . import VMBaseClass
2from .releases import base_vm_classes as relbase2from .releases import base_vm_classes as relbase
33
4import os
5import textwrap4import textwrap
65
76
@@ -29,7 +28,7 @@
29 ls -al /dev/disk/by-uuid/ > ls_uuid28 ls -al /dev/disk/by-uuid/ > ls_uuid
30 ls -al /dev/disk/by-id/ > ls_disk_id29 ls -al /dev/disk/by-id/ > ls_disk_id
31 readlink -f /sys/class/block/sda/holders/dm-0 > holders_sda30 readlink -f /sys/class/block/sda/holders/dm-0 > holders_sda
32 readlink /sys/class/block/sdb/holders/dm-0 > holders_sdb31 readlink -f /sys/class/block/sdb/holders/dm-0 > holders_sdb
33 cat /etc/fstab > fstab32 cat /etc/fstab > fstab
34 mkdir -p /dev/disk/by-dname33 mkdir -p /dev/disk/by-dname
35 ls /dev/disk/by-dname/ > ls_dname34 ls /dev/disk/by-dname/ > ls_dname
@@ -37,17 +36,10 @@
37 """)]36 """)]
3837
39 def test_multipath_disks_match(self):38 def test_multipath_disks_match(self):
40 sda = os.path.join(self.td.collect, 'holders_sda')39 sda_data = self.load_collect_file("holders_sda")
41 sdb = os.path.join(self.td.collect, 'holders_sdb')40 print('sda holders:\n%s' % sda_data)
42 self.assertTrue(os.path.exists(sda))41 sdb_data = self.load_collect_file("holders_sdb")
43 self.assertTrue(os.path.exists(sdb))42 print('sdb holders:\n%s' % sdb_data)
44 with open(sda, 'r') as fp:
45 sda_data = fp.read()
46 print('sda holders:\n%s' % sda_data)
47 with open(sda, 'r') as fp:
48 sdb_data = fp.read()
49 print('sdb holders:\n%s' % sda_data)
50
51 self.assertEqual(sda_data, sdb_data)43 self.assertEqual(sda_data, sdb_data)
5244
5345
5446
=== modified file 'tests/vmtests/test_network.py'
--- tests/vmtests/test_network.py 2016-08-09 15:01:07 +0000
+++ tests/vmtests/test_network.py 2016-08-29 20:01:45 +0000
@@ -4,36 +4,46 @@
4import ipaddress4import ipaddress
5import os5import os
6import re6import re
7import subprocess
8import textwrap7import textwrap
9import yaml8import yaml
109
1110
12class TestNetworkAbs(VMBaseClass):11class TestNetworkBaseTestsAbs(VMBaseClass):
13 interactive = False12 interactive = False
14 conf_file = "examples/tests/basic_network.yaml"
15 extra_disks = []13 extra_disks = []
16 extra_nics = []14 extra_nics = []
17 collect_scripts = [textwrap.dedent("""15 collect_scripts = [textwrap.dedent("""
18 cd OUTPUT_COLLECT_D16 cd OUTPUT_COLLECT_D
17 echo "waiting for ipv6 to settle" && sleep 5
19 ifconfig -a > ifconfig_a18 ifconfig -a > ifconfig_a
19 ip link show > ip_link_show
20 ip a > ip_a
21 find /etc/network/interfaces.d > find_interfacesd
20 cp -av /etc/network/interfaces .22 cp -av /etc/network/interfaces .
21 cp -av /etc/network/interfaces.d .23 cp -av /etc/network/interfaces.d .
22 find /etc/network/interfaces.d > find_interfacesd
23 cp /etc/resolv.conf .24 cp /etc/resolv.conf .
24 cp -av /etc/udev/rules.d/70-persistent-net.rules .25 cp -av /etc/udev/rules.d/70-persistent-net.rules .
25 ip -o route show > ip_route_show26 ip -o route show > ip_route_show
27 ip -6 -o route show > ip_6_route_show
26 route -n > route_n28 route -n > route_n
29 route -6 -n > route_6_n
27 cp -av /run/network ./run_network30 cp -av /run/network ./run_network
31 cp -av /var/log/upstart ./upstart ||:
32 sleep 10 && ip a > ip_a
28 """)]33 """)]
2934
30 def test_output_files_exist(self):35 def test_output_files_exist(self):
31 self.output_files_exist(["ifconfig_a",36 self.output_files_exist([
32 "interfaces",37 "70-persistent-net.rules",
33 "resolv.conf",38 "find_interfacesd",
34 "70-persistent-net.rules",39 "ifconfig_a",
35 "ip_route_show",40 "interfaces",
36 "route_n"])41 "ip_a",
42 "ip_route_show",
43 "resolv.conf",
44 "route_6_n",
45 "route_n",
46 ])
3747
38 def test_etc_network_interfaces(self):48 def test_etc_network_interfaces(self):
39 with open(os.path.join(self.td.collect, "interfaces")) as fp:49 with open(os.path.join(self.td.collect, "interfaces")) as fp:
@@ -76,27 +86,44 @@
76 '''86 '''
77 expected_ifaces = self.get_expected_etc_resolvconf()87 expected_ifaces = self.get_expected_etc_resolvconf()
78 logger.debug('parsed eni ifaces:\n{}'.format(expected_ifaces))88 logger.debug('parsed eni ifaces:\n{}'.format(expected_ifaces))
89
90 def _mk_dns_lines(dns_type, config):
91 """ nameservers get a line per ns
92 search is a space-separated list """
93 lines = []
94 if dns_type == 'nameservers':
95 if ' ' in config:
96 config = config.split()
97 for ns in config:
98 lines.append("nameserver %s" % ns)
99 elif dns_type == 'search':
100 if isinstance(config, list):
101 config = " ".join(config)
102 lines.append("search %s" % config)
103
104 return lines
105
79 for ifname in expected_ifaces.keys():106 for ifname in expected_ifaces.keys():
80 iface = expected_ifaces.get(ifname)107 iface = expected_ifaces.get(ifname)
81 for k, v in iface.get('dns', {}).items():108 for k, v in iface.get('dns', {}).items():
82 dns_line = '{} {}'.format(109 print('k=%s v=%s' % (k, v))
83 k.replace('nameservers', 'nameserver'), " ".join(v))110 for dns_line in _mk_dns_lines(k, v):
84 logger.debug('dns_line:{}'.format(dns_line))111 logger.debug('dns_line:%s', dns_line)
85 self.assertTrue(dns_line in resolv_lines)112 self.assertTrue(dns_line in resolv_lines)
86113
87 def test_ifconfig_output(self):114 def test_ip_output(self):
88 '''check ifconfig output with test input'''115 '''check iproute2 'ip a' output with test input'''
89 network_state = self.get_network_state()116 network_state = self.get_network_state()
90 logger.debug('expected_network_state:\n{}'.format(117 logger.debug('expected_network_state:\n{}'.format(
91 yaml.dump(network_state, default_flow_style=False, indent=4)))118 yaml.dump(network_state, default_flow_style=False, indent=4)))
92119
93 with open(os.path.join(self.td.collect, "ifconfig_a")) as fp:120 with open(os.path.join(self.td.collect, "ip_a")) as fp:
94 ifconfig_a = fp.read()121 ip_a = fp.read()
95 logger.debug('ifconfig -a:\n{}'.format(ifconfig_a))122 logger.debug('ip a:\n{}'.format(ip_a))
96123
97 ifconfig_dict = helpers.ifconfig_to_dict(ifconfig_a)124 ip_dict = helpers.ip_a_to_dict(ip_a)
98 logger.debug('parsed ifcfg dict:\n{}'.format(125 print('parsed ip_a dict:\n{}'.format(
99 yaml.dump(ifconfig_dict, default_flow_style=False, indent=4)))126 yaml.dump(ip_dict, default_flow_style=False, indent=4)))
100127
101 with open(os.path.join(self.td.collect, "ip_route_show")) as fp:128 with open(os.path.join(self.td.collect, "ip_route_show")) as fp:
102 ip_route_show = fp.read()129 ip_route_show = fp.read()
@@ -115,341 +142,167 @@
115 route_n = fp.read()142 route_n = fp.read()
116 logger.debug("route -n:\n{}".format(route_n))143 logger.debug("route -n:\n{}".format(route_n))
117144
145 with open(os.path.join(self.td.collect, "route_6_n")) as fp:
146 route_6_n = fp.read()
147 logger.debug("route -6 -n:\n{}".format(route_6_n))
148
149 routes = {
150 '4': route_n,
151 '6': route_6_n,
152 }
118 interfaces = network_state.get('interfaces')153 interfaces = network_state.get('interfaces')
119 for iface in interfaces.values():154 for iface in interfaces.values():
120 subnets = iface.get('subnets', {})155 print("\nnetwork_state iface: %s" % (
121 if subnets:156 yaml.dump(iface, default_flow_style=False, indent=4)))
122 for index, subnet in zip(range(0, len(subnets)), subnets):157 self.check_interface(iface['name'],
123 iface['index'] = index158 iface,
124 if index == 0:159 ip_dict.get(iface['name']),
125 ifname = "{name}".format(**iface)160 routes)
126 else:161
127 ifname = "{name}:{index}".format(**iface)162 def check_interface(self, ifname, iface, ipcfg, routes):
128163 print('check_interface: testing '
129 self.check_interface(iface,164 'ifname:{}\niface:\n{}\n\nipcfg:\n{}'.format(ifname, iface,
130 ifconfig_dict.get(ifname),165 ipcfg))
131 route_n)166 # FIXME: remove check?
132 else:
133 iface['index'] = 0
134 self.check_interface(iface,
135 ifconfig_dict.get(iface['name']),
136 route_n)
137
138 def check_interface(self, iface, ifconfig, route_n):
139 logger.debug(
140 'testing iface:\n{}\n\nifconfig:\n{}'.format(iface, ifconfig))
141 subnets = iface.get('subnets', {})
142 if subnets and iface['index'] != 0:
143 ifname = "{name}:{index}".format(**iface)
144 else:
145 ifname = "{name}".format(**iface)
146
147 # initial check, do we have the correct iface ?167 # initial check, do we have the correct iface ?
148 logger.debug('ifname={}'.format(ifname))168 print('ifname={}'.format(ifname))
149 logger.debug("ifconfig['interface']={}".format(ifconfig['interface']))169 self.assertEqual(ifname, ipcfg['interface'])
150 self.assertEqual(ifname, ifconfig['interface'])170 print("ipcfg['interface']={}".format(ipcfg['interface']))
151171
152 # check physical interface attributes172 # check physical interface attributes (skip bond members, macs change)
153 for key in ['mac_address', 'mtu']:173 if iface['type'] in ['physical'] and 'bond-master' not in iface:
174 for key in ['mac_address']:
175 print("checking mac on iface: %s" % iface['name'])
176 if key in iface and iface[key]:
177 self.assertEqual(iface[key].lower(),
178 ipcfg[key].lower())
179
180 # we can check mtu on all interfaces
181 for key in ['mtu']:
154 if key in iface and iface[key]:182 if key in iface and iface[key]:
155 self.assertEqual(iface[key],183 print("checking mtu on iface: %s" % iface['name'])
156 ifconfig[key])184 self.assertEqual(int(iface[key]),
157185 int(ipcfg[key]))
158 def __get_subnet(subnets, subidx):186
159 for index, subnet in zip(range(0, len(subnets)), subnets):187 # check subnet related attributes
160 if index == subidx:188 subnets = iface.get('subnets')
161 break189 if subnets is None:
162 return subnet190 subnets = []
163191 for subnet in subnets:
164 # check subnet related attributes, and specifically only192 config_inet_iface = None
165 # the subnet specified by iface['index']193 found_inet_iface = None
166 subnets = iface.get('subnets', {})194 print('validating subnet:\n%s' % subnet)
167 if subnets:
168 subnet = __get_subnet(subnets, iface['index'])
169 if 'address' in subnet and subnet['address']:195 if 'address' in subnet and subnet['address']:
196 # we will create to ipaddress.IPvXInterface objects
197 # one based on config, and other from collected data
198 # and compare.
199 config_ipstr = subnet['address']
200 if 'netmask' in subnet:
201 config_ipstr += "/%s" % subnet['netmask']
202
203 # One more bit is how to construct the
204 # right Version interface, detecting on ":" in address
205 # detect ipv6 or v4
170 if ':' in subnet['address']:206 if ':' in subnet['address']:
171 inet_iface = ipaddress.IPv6Interface(207 # v6
172 subnet['address'])208 config_inet_iface = ipaddress.IPv6Interface(config_ipstr)
173 else:209 ip_func = ipaddress.IPv6Interface
174 inet_iface = ipaddress.IPv4Interface(210 addresses = ipcfg.get('inet6', [])
175 subnet['address'])211 else:
176212 # v4
177 # check ip addr213 config_inet_iface = ipaddress.IPv4Interface(config_ipstr)
178 self.assertEqual(str(inet_iface.ip),214 ip_func = ipaddress.IPv4Interface
179 ifconfig['address'])215 addresses = ipcfg.get('inet4', [])
180216
181 self.assertEqual(str(inet_iface.netmask),217 # find a matching
182 ifconfig['netmask'])218 print('found addresses: %s' % addresses)
183219 for ip in addresses:
184 self.assertEqual(220 print('cur ip=%s\nsubnet=%s' % (ip, subnet))
185 str(inet_iface.network.broadcast_address),221 # drop /CIDR if present for matching
186 ifconfig['broadcast'])222 if (ip['address'].split("/")[0] ==
187223 subnet['address'].split("/")[0]):
188 # handle gateway by looking at routing table224 print('found a match!')
189 if 'gateway' in subnet and subnet['gateway']:225 found_ipstr = ip['address']
190 gw_ip = subnet['gateway']226 if ('netmask' in subnet or '/' in subnet['address']):
191 gateways = [line for line in route_n.split('\n')227 found_ipstr += "/%s" % ip.get('prefixlen')
192 if 'UG' in line and gw_ip in line]228 found_inet_iface = ip_func(found_ipstr)
193 logger.debug('matching gateways:\n{}'.format(gateways))229 print('returning inet iface')
194 self.assertEqual(len(gateways), 1)230 break
195 [gateways] = gateways231
196 (dest, gw, genmask, flags, metric, ref, use, iface) = \232 # check ipaddress interface matches (config vs. found)
197 gateways.split()233 self.assertIsNotNone(config_inet_iface)
198 logger.debug('expected gw:{} found gw:{}'.format(gw_ip, gw))234 self.assertIsNotNone(found_inet_iface)
199 self.assertEqual(gw_ip, gw)235 self.assertEqual(config_inet_iface, found_inet_iface)
200236
201237 def __find_gw_config(subnet):
202class TestNetworkStaticAbs(TestNetworkAbs):238 gateways = []
203 conf_file = "examples/tests/basic_network_static.yaml"239 if 'gateway' in subnet:
204240 gateways.append(subnet.get('gateway'))
205241 for route in subnet.get('routes', []):
206class TestNetworkVlanAbs(TestNetworkAbs):242 gateways += __find_gw_config(route)
207 conf_file = "examples/tests/vlan_network.yaml"243 return gateways
208 collect_scripts = TestNetworkAbs.collect_scripts + [textwrap.dedent("""244
209 cd OUTPUT_COLLECT_D245 # handle gateways by looking at routing table
210 dpkg-query -W -f '${Status}' vlan > vlan_installed246 configured_gws = __find_gw_config(subnet)
211 ip -d link show interface1.2667 > ip_link_show_interface1.2667247 print('iface:%s configured_gws: %s' % (ifname, configured_gws))
212 ip -d link show interface1.2668 > ip_link_show_interface1.2668248 for gw_ip in configured_gws:
213 ip -d link show interface1.2669 > ip_link_show_interface1.2669249 logger.debug('found a gateway in subnet config: %s', gw_ip)
214 ip -d link show interface1.2670 > ip_link_show_interface1.2670250 if ":" in gw_ip:
215 """)]251 route_d = routes['6']
216252 else:
217 def get_vlans(self):253 route_d = routes['4']
218 network_state = self.get_network_state()254
219 logger.debug('get_vlans ns:\n{}'.format(255 found_gws = [line for line in route_d.split('\n')
220 yaml.dump(network_state, default_flow_style=False, indent=4)))256 if 'UG' in line and gw_ip in line]
221 interfaces = network_state.get('interfaces')257 logger.debug('found gateways in guest output:\n%s', found_gws)
222 return [iface for iface in interfaces.values()258
223 if iface['type'] == 'vlan']259 print('found_gws: %s\nexpected: %s' % (found_gws,
224260 configured_gws))
225 def test_output_files_exist_vlan(self):261 self.assertEqual(len(found_gws), len(configured_gws))
226 link_files = ["ip_link_show_{}".format(vlan['name'])262 for fgw in found_gws:
227 for vlan in self.get_vlans()]263 if ":" in gw_ip:
228 self.output_files_exist(["vlan_installed"] + link_files)264 (dest, gw, flags, metric, ref, use, iface) = \
229265 fgw.split()
230 def test_vlan_installed(self):266 else:
231 with open(os.path.join(self.td.collect, "vlan_installed")) as fp:267 (dest, gw, genmask, flags, metric, ref, use, iface) = \
232 status = fp.read().strip()268 fgw.split()
233 logger.debug('vlan installed?: {}'.format(status))269 logger.debug('configured gw:%s found gw:%s', gw_ip, gw)
234 self.assertEqual('install ok installed', status)270 self.assertEqual(gw_ip, gw)
235271
236 def test_vlan_enabled(self):272
237273class TestNetworkBasicAbs(TestNetworkBaseTestsAbs):
238 # we must have at least one274 """ Basic network testing with ipv4
239 self.assertGreaterEqual(len(self.get_vlans()), 1)
240
241 # did they get configured?
242 for vlan in self.get_vlans():
243 link_file = "ip_link_show_" + vlan['name']
244 vlan_msg = "vlan protocol 802.1Q id " + str(vlan['vlan_id'])
245 self.check_file_regex(link_file, vlan_msg)
246
247
248class TestNetworkENISource(TestNetworkAbs):
249 """ Curtin now emits a source /etc/network/interfaces.d/*.cfg
250 line. This test exercises this feature by emitting additional
251 network configuration in /etc/network/interfaces.d/eth2.cfg
252
253 This relies on the network_config.yaml of the TestClass to
254 define a spare nic with no configuration. This ensures that
255 a udev rule for eth2 is emitted so we can reference the interface
256 in our injected configuration.
257
258 Note, ifupdown allows multiple stanzas with the same iface name
259 and combines the options together during ifup. We rely on this
260 feature allowing etc/network/interfaces to have an unconfigured
261 iface eth2 inet manual line, and then defer the configuration
262 to /etc/network/interfaces.d/eth2.cfg
263
264 This testcase then uses curtin.net.deb_parse_config method to
265 extract information about what curtin wrote and compare that
266 with what was actually configured (which we capture via ifconfig)
267 """275 """
268276 conf_file = "examples/tests/basic_network.yaml"
269 conf_file = "examples/tests/network_source.yaml"277
270 collect_scripts = [textwrap.dedent("""278
271 cd OUTPUT_COLLECT_D279class PreciseHWETTestNetworkBasic(relbase.precise_hwe_t, TestNetworkBasicAbs):
272 ifconfig -a > ifconfig_a280 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
273 cp -av /etc/network/interfaces .281 __test__ = False
274 cp -a /etc/network/interfaces.d .282
275 find /etc/network/interfaces.d > find_interfacesd283
276 cp /etc/resolv.conf .284class TrustyTestNetworkBasic(relbase.trusty, TestNetworkBasicAbs):
277 cp -av /etc/udev/rules.d/70-persistent-net.rules .285 __test__ = True
278 ip -o route show > ip_route_show286
279 route -n > route_n287
280 """)]288class TrustyHWEUTestNetworkBasic(relbase.trusty_hwe_u, TrustyTestNetworkBasic):
281289 # Working, off by default to safe test suite runtime, covered by bonding
282 def test_source_cfg_exists(self):290 __test__ = False
283 """Test that our curthooks wrote our injected config."""291
284 self.output_files_exist(["interfaces.d/interface2.cfg"])292
285293class TrustyHWEVTestNetworkBasic(relbase.trusty_hwe_v, TrustyTestNetworkBasic):
286 def test_etc_network_interfaces_source_cfg(self):294 # Working, off by default to safe test suite runtime, covered by bonding
287 """ Compare injected configuration as parsed by curtin matches295 __test__ = False
288 how ifup configured the interface."""296
289 # interfaces uses absolute paths, fix for test-case297
290 interfaces = os.path.join(self.td.collect, "interfaces")298class TrustyHWEWTestNetworkBasic(relbase.trusty_hwe_w, TrustyTestNetworkBasic):
291 cmd = ['sed', '-i.orig', '-e', 's,/etc/network/,,g',299 # Working, off by default to safe test suite runtime, covered by bonding
292 '{}'.format(interfaces)]300 __test__ = False
293 subprocess.check_call(cmd, stderr=subprocess.STDOUT)301
294302
295 curtin_ifaces = self.parse_deb_config(interfaces)303class XenialTestNetworkBasic(relbase.xenial, TestNetworkBasicAbs):
296 logger.debug('parsed eni dict:\n{}'.format(304 __test__ = True
297 yaml.dump(curtin_ifaces, default_flow_style=False, indent=4)))305
298 print('parsed eni dict:\n{}'.format(306
299 yaml.dump(curtin_ifaces, default_flow_style=False, indent=4)))307class YakketyTestNetworkBasic(relbase.yakkety, TestNetworkBasicAbs):
300
301 with open(os.path.join(self.td.collect, "ifconfig_a")) as fp:
302 ifconfig_a = fp.read()
303 logger.debug('ifconfig -a:\n{}'.format(ifconfig_a))
304
305 ifconfig_dict = helpers.ifconfig_to_dict(ifconfig_a)
306 logger.debug('parsed ifconfig dict:\n{}'.format(
307 yaml.dump(ifconfig_dict, default_flow_style=False, indent=4)))
308 print('parsed ifconfig dict:\n{}'.format(
309 yaml.dump(ifconfig_dict, default_flow_style=False, indent=4)))
310
311 iface = 'interface2'
312 self.assertTrue(iface in curtin_ifaces)
313
314 expected_address = curtin_ifaces[iface].get('address', None)
315 self.assertIsNotNone(expected_address)
316
317 # handle CIDR notation
318 def _nocidr(addr):
319 return addr.split("/")[0]
320 actual_address = ifconfig_dict[iface].get('address', "")
321 self.assertEqual(_nocidr(expected_address), _nocidr(actual_address))
322
323
324class PreciseHWETTestNetwork(relbase.precise_hwe_t, TestNetworkAbs):
325 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
326 __test__ = False
327
328
329class PreciseHWETTestNetworkStatic(relbase.precise_hwe_t,
330 TestNetworkStaticAbs):
331 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
332 __test__ = False
333
334
335class TrustyTestNetwork(relbase.trusty, TestNetworkAbs):
336 __test__ = True
337
338
339class TrustyTestNetworkStatic(relbase.trusty, TestNetworkStaticAbs):
340 __test__ = True
341
342
343class TrustyHWEUTestNetwork(relbase.trusty_hwe_u, TrustyTestNetwork):
344 # Working, off by default to safe test suite runtime, covered by bonding
345 __test__ = False
346
347
348class TrustyHWEUTestNetworkStatic(relbase.trusty_hwe_u,
349 TestNetworkStaticAbs):
350 # Working, off by default to safe test suite runtime, covered by bonding
351 __test__ = False
352
353
354class TrustyHWEVTestNetwork(relbase.trusty_hwe_v, TrustyTestNetwork):
355 # Working, off by default to safe test suite runtime, covered by bonding
356 __test__ = False
357
358
359class TrustyHWEVTestNetworkStatic(relbase.trusty_hwe_v,
360 TestNetworkStaticAbs):
361 # Working, off by default to safe test suite runtime, covered by bonding
362 __test__ = False
363
364
365class TrustyHWEWTestNetwork(relbase.trusty_hwe_w, TrustyTestNetwork):
366 # Working, off by default to safe test suite runtime, covered by bonding
367 __test__ = False
368
369
370class TrustyHWEWTestNetworkStatic(relbase.trusty_hwe_w,
371 TestNetworkStaticAbs):
372 # Working, off by default to safe test suite runtime, covered by bonding
373 __test__ = False
374
375
376class WilyTestNetwork(relbase.wily, TestNetworkAbs):
377 # EOL - 2016-07-28
378 __test__ = False
379
380
381class WilyTestNetworkStatic(relbase.wily, TestNetworkStaticAbs):
382 # EOL - 2016-07-28
383 __test__ = False
384
385
386class XenialTestNetwork(relbase.xenial, TestNetworkAbs):
387 __test__ = True
388
389
390class XenialTestNetworkStatic(relbase.xenial, TestNetworkStaticAbs):
391 __test__ = True
392
393
394class YakketyTestNetwork(relbase.yakkety, TestNetworkAbs):
395 __test__ = True
396
397
398class YakketyTestNetworkStatic(relbase.yakkety, TestNetworkStaticAbs):
399 __test__ = True
400
401
402class PreciseTestNetworkVlan(relbase.precise, TestNetworkVlanAbs):
403 __test__ = True
404
405 # precise ip -d link show output is different (of course)
406 def test_vlan_enabled(self):
407
408 # we must have at least one
409 self.assertGreaterEqual(len(self.get_vlans()), 1)
410
411 # did they get configured?
412 for vlan in self.get_vlans():
413 link_file = "ip_link_show_" + vlan['name']
414 vlan_msg = "vlan id " + str(vlan['vlan_id'])
415 self.check_file_regex(link_file, vlan_msg)
416
417
418class TrustyTestNetworkVlan(relbase.trusty, TestNetworkVlanAbs):
419 __test__ = True
420
421
422class WilyTestNetworkVlan(relbase.wily, TestNetworkVlanAbs):
423 # EOL - 2016-07-28
424 __test__ = False
425
426
427class XenialTestNetworkVlan(relbase.xenial, TestNetworkVlanAbs):
428 __test__ = True
429
430
431class YakketyTestNetworkVlan(relbase.yakkety, TestNetworkVlanAbs):
432 __test__ = True
433
434
435class PreciseTestNetworkENISource(relbase.precise, TestNetworkENISource):
436 __test__ = False
437 # not working, still debugging though; possible older ifupdown doesn't
438 # like the multiple iface method.
439
440
441class TrustyTestNetworkENISource(relbase.trusty, TestNetworkENISource):
442 __test__ = True
443
444
445class WilyTestNetworkENISource(relbase.wily, TestNetworkENISource):
446 # EOL - 2016-07-28
447 __test__ = False
448
449
450class XenialTestNetworkENISource(relbase.xenial, TestNetworkENISource):
451 __test__ = True
452
453
454class YakketyTestNetworkENISource(relbase.yakkety, TestNetworkENISource):
455 __test__ = True308 __test__ = True
456309
=== added file 'tests/vmtests/test_network_alias.py'
--- tests/vmtests/test_network_alias.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_alias.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,40 @@
1from .releases import base_vm_classes as relbase
2from .test_network import TestNetworkBaseTestsAbs
3
4
5class TestNetworkAliasAbs(TestNetworkBaseTestsAbs):
6 """ Multi-ip address network testing
7 """
8 conf_file = "examples/tests/network_alias.yaml"
9
10
11class PreciseHWETTestNetworkAlias(relbase.precise_hwe_t, TestNetworkAliasAbs):
12 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
13 __test__ = True
14
15
16class TrustyTestNetworkAlias(relbase.trusty, TestNetworkAliasAbs):
17 __test__ = True
18
19
20class TrustyHWEUTestNetworkAlias(relbase.trusty_hwe_u, TrustyTestNetworkAlias):
21 # Working, off by default to safe test suite runtime, covered by bonding
22 __test__ = False
23
24
25class TrustyHWEVTestNetworkAlias(relbase.trusty_hwe_v, TrustyTestNetworkAlias):
26 # Working, off by default to safe test suite runtime, covered by bonding
27 __test__ = False
28
29
30class TrustyHWEWTestNetworkAlias(relbase.trusty_hwe_w, TrustyTestNetworkAlias):
31 # Working, off by default to safe test suite runtime, covered by bonding
32 __test__ = False
33
34
35class XenialTestNetworkAlias(relbase.xenial, TestNetworkAliasAbs):
36 __test__ = True
37
38
39class YakketyTestNetworkAlias(relbase.yakkety, TestNetworkAliasAbs):
40 __test__ = True
041
=== added file 'tests/vmtests/test_network_bonding.py'
--- tests/vmtests/test_network_bonding.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_bonding.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,63 @@
1from . import logger
2from .releases import base_vm_classes as relbase
3from .test_network import TestNetworkBaseTestsAbs
4
5import textwrap
6
7
8class TestNetworkBondingAbs(TestNetworkBaseTestsAbs):
9 conf_file = "examples/tests/bonding_network.yaml"
10 collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
11 textwrap.dedent("""
12 cd OUTPUT_COLLECT_D
13 dpkg-query -W -f '${Status}' ifenslave > ifenslave_installed
14 """)]
15
16 def test_output_files_exist_ifenslave(self):
17 self.output_files_exist(["ifenslave_installed"])
18
19 def test_ifenslave_installed(self):
20 status = self.load_collect_file("ifenslave_installed")
21 logger.debug('ifenslave installed: {}'.format(status))
22 self.assertEqual('install ok installed', status)
23
24
25class PreciseHWETTestBonding(relbase.precise_hwe_t, TestNetworkBondingAbs):
26 __test__ = True
27 # package names on precise are different, need to check on ifenslave-2.6
28 collect_scripts = TestNetworkBondingAbs.collect_scripts + [
29 textwrap.dedent("""
30 cd OUTPUT_COLLECT_D
31 dpkg-query -W -f '${Status}' ifenslave-2.6 > ifenslave_installed
32 """)]
33
34
35class TrustyTestBonding(relbase.trusty, TestNetworkBondingAbs):
36 __test__ = False
37
38
39class TrustyHWEUTestBonding(relbase.trusty_hwe_u, TrustyTestBonding):
40 __test__ = True
41
42
43class TrustyHWEVTestBonding(relbase.trusty_hwe_v, TrustyTestBonding):
44 # Working, but off by default to safe test suite runtime
45 # oldest/newest HWE-* covered above/below
46 __test__ = False
47
48
49class TrustyHWEWTestBonding(relbase.trusty_hwe_w, TrustyTestBonding):
50 __test__ = True
51
52
53class WilyTestBonding(relbase.wily, TestNetworkBondingAbs):
54 # EOL - 2016-07-28
55 __test__ = False
56
57
58class XenialTestBonding(relbase.xenial, TestNetworkBondingAbs):
59 __test__ = True
60
61
62class YakketyTestBonding(relbase.yakkety, TestNetworkBondingAbs):
63 __test__ = True
064
=== added file 'tests/vmtests/test_network_enisource.py'
--- tests/vmtests/test_network_enisource.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_enisource.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,91 @@
1from . import logger, helpers
2from .releases import base_vm_classes as relbase
3from .test_network import TestNetworkBaseTestsAbs
4
5import os
6import subprocess
7import yaml
8
9
10class TestNetworkENISource(TestNetworkBaseTestsAbs):
11 """ Curtin now emits a source /etc/network/interfaces.d/*.cfg
12 line. This test exercises this feature by emitting additional
13 network configuration in /etc/network/interfaces.d/interface2.cfg
14
15 This relies on the network_config.yaml of the TestClass to
16 define a spare nic with no configuration. This ensures that
17 a udev rule for interface2 is emitted so we can reference the interface
18 in our injected configuration.
19
20 Note, ifupdown allows multiple stanzas with the same iface name
21 and combines the options together during ifup. We rely on this
22 feature allowing etc/network/interfaces to have an unconfigured
23 iface interface2 inet manual line, and then defer the configuration
24 to /etc/network/interfaces.d/interface2.cfg
25
26 This testcase then uses curtin.net.deb_parse_config method to
27 extract information about what curtin wrote and compare that
28 with what was actually configured (which we capture via ifconfig)
29 """
30
31 conf_file = "examples/tests/network_source.yaml"
32
33 def test_source_cfg_exists(self):
34 """Test that our curthooks wrote our injected config."""
35 self.output_files_exist(["interfaces.d/interface2.cfg"])
36
37 def test_etc_network_interfaces_source_cfg(self):
38 """ Compare injected configuration as parsed by curtin matches
39 how ifup configured the interface."""
40 # interfaces uses absolute paths, fix for test-case
41 interfaces = os.path.join(self.td.collect, "interfaces")
42 cmd = ['sed', '-i.orig', '-e', 's,/etc/network/,,g',
43 '{}'.format(interfaces)]
44 subprocess.check_call(cmd, stderr=subprocess.STDOUT)
45
46 curtin_ifaces = self.parse_deb_config(interfaces)
47 logger.debug('parsed eni dict:\n{}'.format(
48 yaml.dump(curtin_ifaces, default_flow_style=False, indent=4)))
49 print('parsed eni dict:\n{}'.format(
50 yaml.dump(curtin_ifaces, default_flow_style=False, indent=4)))
51
52 ip_a = self.load_collect_file("ip_a")
53 logger.debug('ip a:\n{}'.format(ip_a))
54
55 ip_a_dict = helpers.ip_a_to_dict(ip_a)
56 logger.debug('parsed ip_a dict:\n{}'.format(
57 yaml.dump(ip_a_dict, default_flow_style=False, indent=4)))
58 print('parsed ip_a dict:\n{}'.format(
59 yaml.dump(ip_a_dict, default_flow_style=False, indent=4)))
60
61 iface = 'interface2'
62 self.assertTrue(iface in curtin_ifaces)
63
64 expected_address = curtin_ifaces[iface].get('address', None)
65 self.assertIsNotNone(expected_address)
66
67 # handle CIDR notation
68 def _nocidr(addr):
69 return addr.split("/")[0]
70
71 [actual_address] = [ip.get('address') for ip in
72 ip_a_dict[iface].get('inet4', [])]
73 self.assertEqual(_nocidr(expected_address), _nocidr(actual_address))
74
75
76class PreciseTestNetworkENISource(relbase.precise, TestNetworkENISource):
77 __test__ = False
78 # not working, still debugging though; possible older ifupdown doesn't
79 # like the multiple iface method.
80
81
82class TrustyTestNetworkENISource(relbase.trusty, TestNetworkENISource):
83 __test__ = True
84
85
86class XenialTestNetworkENISource(relbase.xenial, TestNetworkENISource):
87 __test__ = True
88
89
90class YakketyTestNetworkENISource(relbase.yakkety, TestNetworkENISource):
91 __test__ = True
092
=== added file 'tests/vmtests/test_network_ipv6.py'
--- tests/vmtests/test_network_ipv6.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_ipv6.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,53 @@
1from .releases import base_vm_classes as relbase
2from .test_network import TestNetworkBaseTestsAbs
3
4import textwrap
5
6
7class TestNetworkIPV6Abs(TestNetworkBaseTestsAbs):
8 """ IPV6 complex testing. The configuration exercises
9 - ipv4 and ipv6 address on same interface
10 - bonding in LACP mode
11 - unconfigured subnets on bond
12 - vlans over bonds
13 - all IP is static
14 """
15 conf_file = "examples/network-ipv6-bond-vlan.yaml"
16 collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
17 textwrap.dedent("""
18 grep . -r /sys/class/net/bond0/ > sysfs_bond0 || :
19 grep . -r /sys/class/net/bond0.108/ > sysfs_bond0.108 || :
20 grep . -r /sys/class/net/bond0.208/ > sysfs_bond0.208 || :
21 """)]
22
23
24class PreciseHWETTestNetwork(relbase.precise_hwe_t, TestNetworkIPV6Abs):
25 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
26 __test__ = False
27
28
29class TrustyTestNetworkIPV6(relbase.trusty, TestNetworkIPV6Abs):
30 __test__ = True
31
32
33class TrustyHWEUTestNetworkIPV6(relbase.trusty_hwe_u, TrustyTestNetworkIPV6):
34 # Working, off by default to safe test suite runtime, covered by bonding
35 __test__ = False
36
37
38class TrustyHWEVTestNetworkIPV6(relbase.trusty_hwe_v, TrustyTestNetworkIPV6):
39 # Working, off by default to safe test suite runtime, covered by bonding
40 __test__ = False
41
42
43class TrustyHWEWTestNetworkIPV6(relbase.trusty_hwe_w, TrustyTestNetworkIPV6):
44 # Working, off by default to safe test suite runtime, covered by bonding
45 __test__ = False
46
47
48class XenialTestNetworkIPV6(relbase.xenial, TestNetworkIPV6Abs):
49 __test__ = True
50
51
52class YakketyTestNetworkIPV6(relbase.yakkety, TestNetworkIPV6Abs):
53 __test__ = True
054
=== added file 'tests/vmtests/test_network_ipv6_enisource.py'
--- tests/vmtests/test_network_ipv6_enisource.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_ipv6_enisource.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,26 @@
1from .releases import base_vm_classes as relbase
2from .test_network_enisource import TestNetworkENISource
3
4
5class TestNetworkIPV6ENISource(TestNetworkENISource):
6 conf_file = "examples/tests/network_source_ipv6.yaml"
7
8
9class PreciseTestNetworkIPV6ENISource(relbase.precise,
10 TestNetworkIPV6ENISource):
11 __test__ = False
12 # not working, still debugging though; possible older ifupdown doesn't
13 # like the multiple iface method.
14
15
16class TrustyTestNetworkIPV6ENISource(relbase.trusty, TestNetworkIPV6ENISource):
17 __test__ = True
18
19
20class XenialTestNetworkIPV6ENISource(relbase.xenial, TestNetworkIPV6ENISource):
21 __test__ = True
22
23
24class YakketyTestNetworkIPV6ENISource(relbase.yakkety,
25 TestNetworkIPV6ENISource):
26 __test__ = True
027
=== added file 'tests/vmtests/test_network_ipv6_static.py'
--- tests/vmtests/test_network_ipv6_static.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_ipv6_static.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,42 @@
1from .releases import base_vm_classes as relbase
2from .test_network_static import TestNetworkStaticAbs
3
4
5# reuse basic network tests but with different config (static, no dhcp)
6class TestNetworkIPV6StaticAbs(TestNetworkStaticAbs):
7 conf_file = "examples/tests/basic_network_static_ipv6.yaml"
8
9
10class PreciseHWETTestNetworkIPV6Static(relbase.precise_hwe_t,
11 TestNetworkIPV6StaticAbs):
12 __test__ = True
13
14
15class TrustyTestNetworkIPV6Static(relbase.trusty, TestNetworkIPV6StaticAbs):
16 __test__ = True
17
18
19class TrustyHWEUTestNetworkIPV6Static(relbase.trusty_hwe_u,
20 TestNetworkIPV6StaticAbs):
21 # unsupported kernel, 2016-08
22 __test__ = False
23
24
25class TrustyHWEVTestNetworkIPV6Static(relbase.trusty_hwe_v,
26 TestNetworkIPV6StaticAbs):
27 # unsupported kernel, 2016-08
28 __test__ = False
29
30
31class TrustyHWEWTestNetworkIPV6Static(relbase.trusty_hwe_w,
32 TestNetworkIPV6StaticAbs):
33 # unsupported kernel, 2016-08
34 __test__ = False
35
36
37class XenialTestNetworkIPV6Static(relbase.xenial, TestNetworkIPV6StaticAbs):
38 __test__ = True
39
40
41class YakketyTestNetworkIPV6Static(relbase.yakkety, TestNetworkIPV6StaticAbs):
42 __test__ = True
043
=== added file 'tests/vmtests/test_network_ipv6_vlan.py'
--- tests/vmtests/test_network_ipv6_vlan.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_ipv6_vlan.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,34 @@
1from .releases import base_vm_classes as relbase
2from .test_network_vlan import TestNetworkVlanAbs
3
4
5class TestNetworkIPV6VlanAbs(TestNetworkVlanAbs):
6 conf_file = "examples/tests/vlan_network_ipv6.yaml"
7
8
9class PreciseTestNetworkIPV6Vlan(relbase.precise, TestNetworkIPV6VlanAbs):
10 __test__ = True
11
12 # precise ip -d link show output is different (of course)
13 def test_vlan_enabled(self):
14
15 # we must have at least one
16 self.assertGreaterEqual(len(self.get_vlans()), 1)
17
18 # did they get configured?
19 for vlan in self.get_vlans():
20 link_file = "ip_link_show_" + vlan['name']
21 vlan_msg = "vlan id " + str(vlan['vlan_id'])
22 self.check_file_regex(link_file, vlan_msg)
23
24
25class TrustyTestNetworkIPV6Vlan(relbase.trusty, TestNetworkIPV6VlanAbs):
26 __test__ = True
27
28
29class XenialTestNetworkIPV6Vlan(relbase.xenial, TestNetworkIPV6VlanAbs):
30 __test__ = True
31
32
33class YakketyTestNetworkIPV6Vlan(relbase.yakkety, TestNetworkIPV6VlanAbs):
34 __test__ = True
035
=== added file 'tests/vmtests/test_network_mtu.py'
--- tests/vmtests/test_network_mtu.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_mtu.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,155 @@
1from .releases import base_vm_classes as relbase
2from .test_network_ipv6 import TestNetworkIPV6Abs
3from curtin import util
4
5import os
6import textwrap
7
8
9class TestNetworkMtuAbs(TestNetworkIPV6Abs):
10 """ Test that the mtu of the ipv6 address is properly
11
12 1. devices default MTU to 1500, test if mtu under
13 inet6 stanza can be set separately from device
14 mtu (works on Xenial and newer ifupdown), check
15 via sysctl.
16
17 2. if ipv6 mtu is > than underlying device, this fails
18 and is unnoticed, ifupdown/hook should fix by changing
19 mtu of underlying device to the same size as the ipv6
20 mtu
21
22 3. order of the v4 vs. v6 stanzas could affect final mtu
23 ipv6 first, then ipv4 with mtu.
24 """
25 conf_file = "examples/tests/network_mtu.yaml"
26 collect_scripts = TestNetworkIPV6Abs.collect_scripts + [textwrap.dedent("""
27 cd OUTPUT_COLLECT_D
28 proc_v6="/proc/sys/net/ipv6/conf"
29 for f in `seq 0 7`; do
30 cat /sys/class/net/interface${f}/mtu > interface${f}_dev_mtu;
31 cat $proc_v6/interface${f}/mtu > interface${f}_ipv6_mtu;
32 done
33 if [ -e /var/log/upstart ]; then
34 cp -a /var/log/upstart ./var_log_upstart
35 fi
36 """)]
37
38 def _load_mtu_data(self, ifname):
39 """ load mtu related files by interface name.
40 returns a dictionary with the follwing
41 keys: 'device', and 'ipv6'. """
42
43 mtu_fn = {
44 'device': "%s_dev_mtu" % ifname,
45 'ipv6': "%s_ipv6_mtu" % ifname,
46 }
47 mtu_val = {}
48 for fnk in mtu_fn.keys():
49 fn = os.path.join(self.td.collect, mtu_fn[fnk])
50 mtu_val.update({fnk: int(util.load_file(fn))})
51
52 return mtu_val
53
54 def _check_subnet_mtu(self, subnet, iface):
55 mtu_data = self._load_mtu_data(iface['name'])
56 print('subnet:%s' % subnet)
57 print('mtu_data:%s' % mtu_data)
58 # ipv4 address mtu changes *device* mtu
59 if '.' in subnet['address']:
60 print('subnet_mtu=%s device_mtu=%s' % (int(subnet['mtu']),
61 int(mtu_data['device'])))
62 self.assertEqual(int(subnet['mtu']),
63 int(mtu_data['device']))
64 # ipv6 address mtu changes *protocol* mtu
65 elif ':' in subnet['address']:
66 print('subnet_mtu=%s ipv6_mtu=%s' % (int(subnet['mtu']),
67 int(mtu_data['device'])))
68 self.assertEqual(int(subnet['mtu']),
69 int(mtu_data['ipv6']))
70
71 def _check_iface_subnets(self, ifname):
72 network_state = self.get_network_state()
73 interfaces = network_state.get('interfaces')
74
75 iface = interfaces.get(ifname)
76 subnets = iface.get('subnets')
77 print('iface=%s subnets=%s' % (iface['name'], subnets))
78 for subnet in subnets:
79 if 'mtu' in subnet:
80 self._check_subnet_mtu(subnet, iface)
81
82 def _disabled_ipv4_and_ipv6_mtu_all(self):
83 """ we don't pass all tests, skip for now """
84 network_state = self.get_network_state()
85 interfaces = network_state.get('interfaces')
86
87 for iface in interfaces.values():
88 subnets = iface.get('subnets', {})
89 if subnets:
90 for index, subnet in zip(range(0, len(subnets)), subnets):
91 print("iface=%s subnet=%s" % (iface['name'], subnet))
92 if 'mtu' in subnet:
93 self._check_subnet_mtu(subnet, iface)
94
95 def test_ipv6_mtu_smaller_than_ipv4_non_default(self):
96 self._check_iface_subnets('interface0')
97
98 def test_ipv6_mtu_equal_ipv4_non_default(self):
99 self._check_iface_subnets('interface1')
100
101 def test_ipv6_mtu_higher_than_default_no_ipv4_mtu(self):
102 self._check_iface_subnets('interface2')
103
104 def test_ipv6_mtu_higher_than_default_no_ipv4_iface_up(self):
105 self._check_iface_subnets('interface3')
106
107 def test_ipv6_mtu_smaller_than_ipv4_v6_iface_first(self):
108 self._check_iface_subnets('interface4')
109
110 def test_ipv6_mtu_equal_ipv4_non_default_v6_iface_first(self):
111 self._check_iface_subnets('interface5')
112
113 def test_ipv6_mtu_higher_than_default_no_ipv4_mtu_v6_iface_first(self):
114 self._check_iface_subnets('interface6')
115
116 def test_ipv6_mtu_higher_than_default_no_ipv4_iface_v6_iface_first(self):
117 self._check_iface_subnets('interface7')
118
119
120class PreciseHWETTestNetworkMtu(relbase.precise_hwe_t, TestNetworkMtuAbs):
121 # FIXME: Precise mtu / ipv6 is buggy
122 __test__ = False
123
124
125class TrustyTestNetworkMtu(relbase.trusty, TestNetworkMtuAbs):
126 __test__ = True
127
128 # FIXME: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=809714
129 # fixed in newer ifupdown than is in trusty
130 def test_ipv6_mtu_smaller_than_ipv4_non_default(self):
131 # trusty ifupdown uses device mtu to change v6 mtu
132 pass
133
134
135class TrustyHWEUTestNetworkMtu(relbase.trusty_hwe_u, TrustyTestNetworkMtu):
136 # unsupported kernel, 2016-08
137 __test__ = False
138
139
140class TrustyHWEVTestNetworkMtu(relbase.trusty_hwe_v, TrustyTestNetworkMtu):
141 # unsupported kernel, 2016-08
142 __test__ = False
143
144
145class TrustyHWEWTestNetworkMtu(relbase.trusty_hwe_w, TrustyTestNetworkMtu):
146 # unsupported kernel, 2016-08
147 __test__ = False
148
149
150class XenialTestNetworkMtu(relbase.xenial, TestNetworkMtuAbs):
151 __test__ = True
152
153
154class YakketyTestNetworkMtu(relbase.yakkety, TestNetworkMtuAbs):
155 __test__ = True
0156
=== added file 'tests/vmtests/test_network_static.py'
--- tests/vmtests/test_network_static.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_static.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,44 @@
1from .releases import base_vm_classes as relbase
2from .test_network import TestNetworkBaseTestsAbs
3
4
5class TestNetworkStaticAbs(TestNetworkBaseTestsAbs):
6 """ Static network testing with ipv4
7 """
8 conf_file = "examples/tests/basic_network_static.yaml"
9
10
11class PreciseHWETTestNetworkStatic(relbase.precise_hwe_t,
12 TestNetworkStaticAbs):
13 # FIXME: off due to hang at test: Starting execute cloud user/final scripts
14 __test__ = False
15
16
17class TrustyTestNetworkStatic(relbase.trusty, TestNetworkStaticAbs):
18 __test__ = True
19
20
21class TrustyHWEUTestNetworkStatic(relbase.trusty_hwe_u,
22 TrustyTestNetworkStatic):
23 # Working, off by default to safe test suite runtime, covered by bonding
24 __test__ = False
25
26
27class TrustyHWEVTestNetworkStatic(relbase.trusty_hwe_v,
28 TrustyTestNetworkStatic):
29 # Working, off by default to safe test suite runtime, covered by bonding
30 __test__ = False
31
32
33class TrustyHWEWTestNetworkStatic(relbase.trusty_hwe_w,
34 TrustyTestNetworkStatic):
35 # Working, off by default to safe test suite runtime, covered by bonding
36 __test__ = False
37
38
39class XenialTestNetworkStatic(relbase.xenial, TestNetworkStaticAbs):
40 __test__ = True
41
42
43class YakketyTestNetworkStatic(relbase.yakkety, TestNetworkStaticAbs):
44 __test__ = True
045
=== added file 'tests/vmtests/test_network_vlan.py'
--- tests/vmtests/test_network_vlan.py 1970-01-01 00:00:00 +0000
+++ tests/vmtests/test_network_vlan.py 2016-08-29 20:01:45 +0000
@@ -0,0 +1,77 @@
1from . import logger
2from .releases import base_vm_classes as relbase
3from .test_network import TestNetworkBaseTestsAbs
4
5import textwrap
6import yaml
7
8
9class TestNetworkVlanAbs(TestNetworkBaseTestsAbs):
10 conf_file = "examples/tests/vlan_network.yaml"
11 collect_scripts = TestNetworkBaseTestsAbs.collect_scripts + [
12 textwrap.dedent("""
13 cd OUTPUT_COLLECT_D
14 dpkg-query -W -f '${Status}' vlan > vlan_installed
15 ip -d link show interface1.2667 > ip_link_show_interface1.2667
16 ip -d link show interface1.2668 > ip_link_show_interface1.2668
17 ip -d link show interface1.2669 > ip_link_show_interface1.2669
18 ip -d link show interface1.2670 > ip_link_show_interface1.2670
19 """)]
20
21 def get_vlans(self):
22 network_state = self.get_network_state()
23 logger.debug('get_vlans ns:\n%s', yaml.dump(network_state,
24 default_flow_style=False,
25 indent=4))
26 interfaces = network_state.get('interfaces')
27 return [iface for iface in interfaces.values()
28 if iface['type'] == 'vlan']
29
30 def test_output_files_exist_vlan(self):
31 link_files = ["ip_link_show_%s" % vlan['name']
32 for vlan in self.get_vlans()]
33 self.output_files_exist(["vlan_installed"] + link_files)
34
35 def test_vlan_installed(self):
36 status = self.load_collect_file("vlan_installed").strip()
37 logger.debug('vlan installed?: %s', status)
38 self.assertEqual('install ok installed', status)
39
40 def test_vlan_enabled(self):
41
42 # we must have at least one
43 self.assertGreaterEqual(len(self.get_vlans()), 1)
44
45 # did they get configured?
46 for vlan in self.get_vlans():
47 link_file = "ip_link_show_" + vlan['name']
48 vlan_msg = "vlan protocol 802.1Q id " + str(vlan['vlan_id'])
49 self.check_file_regex(link_file, vlan_msg)
50
51
52class PreciseTestNetworkVlan(relbase.precise, TestNetworkVlanAbs):
53 __test__ = True
54
55 # precise ip -d link show output is different (of course)
56 def test_vlan_enabled(self):
57
58 # we must have at least one
59 self.assertGreaterEqual(len(self.get_vlans()), 1)
60
61 # did they get configured?
62 for vlan in self.get_vlans():
63 link_file = "ip_link_show_" + vlan['name']
64 vlan_msg = "vlan id " + str(vlan['vlan_id'])
65 self.check_file_regex(link_file, vlan_msg)
66
67
68class TrustyTestNetworkVlan(relbase.trusty, TestNetworkVlanAbs):
69 __test__ = True
70
71
72class XenialTestNetworkVlan(relbase.xenial, TestNetworkVlanAbs):
73 __test__ = True
74
75
76class YakketyTestNetworkVlan(relbase.yakkety, TestNetworkVlanAbs):
77 __test__ = True
078
=== modified file 'tests/vmtests/test_raid5_bcache.py'
--- tests/vmtests/test_raid5_bcache.py 2016-06-13 20:49:15 +0000
+++ tests/vmtests/test_raid5_bcache.py 2016-08-29 20:01:45 +0000
@@ -2,7 +2,6 @@
2from .releases import base_vm_classes as relbase2from .releases import base_vm_classes as relbase
33
4import textwrap4import textwrap
5import os
65
76
8class TestMdadmAbs(VMBaseClass):7class TestMdadmAbs(VMBaseClass):
@@ -55,14 +54,12 @@
5554
56 def test_bcache_status(self):55 def test_bcache_status(self):
57 bcache_cset_uuid = None56 bcache_cset_uuid = None
58 fname = os.path.join(self.td.collect, "bcache_super_vda2")57 for line in self.load_collect_file("bcache_super_vda2").splitlines():
59 with open(fname, "r") as fp:58 if line != "" and line.split()[0] == "cset.uuid":
60 for line in fp.read().splitlines():59 bcache_cset_uuid = line.split()[-1].rstrip()
61 if line != "" and line.split()[0] == "cset.uuid":
62 bcache_cset_uuid = line.split()[-1].rstrip()
63 self.assertIsNotNone(bcache_cset_uuid)60 self.assertIsNotNone(bcache_cset_uuid)
64 with open(os.path.join(self.td.collect, "bcache_ls"), "r") as fp:61 self.assertTrue(bcache_cset_uuid in
65 self.assertTrue(bcache_cset_uuid in fp.read().splitlines())62 self.load_collect_file("bcache_ls").splitlines())
6663
67 def test_bcache_cachemode(self):64 def test_bcache_cachemode(self):
68 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")65 self.check_file_regex("bcache_cache_mode", r"\[writeback\]")
6966
=== modified file 'tests/vmtests/test_uefi_basic.py'
--- tests/vmtests/test_uefi_basic.py 2016-08-09 15:01:07 +0000
+++ tests/vmtests/test_uefi_basic.py 2016-08-29 20:01:45 +0000
@@ -2,7 +2,6 @@
22
3from .releases import base_vm_classes as relbase3from .releases import base_vm_classes as relbase
44
5import os
6import textwrap5import textwrap
76
87
@@ -40,7 +39,7 @@
40 "proc_partitions"])39 "proc_partitions"])
4140
42 def test_sys_firmware_efi(self):41 def test_sys_firmware_efi(self):
43 sys_efi_expected = [42 sys_efi_possible = [
44 'config_table',43 'config_table',
45 'efivars',44 'efivars',
46 'fw_platform_size',45 'fw_platform_size',
@@ -50,22 +49,20 @@
50 'systab',49 'systab',
51 'vars',50 'vars',
52 ]51 ]
53 sys_efi = self.td.collect + "ls_sys_firmware_efi"52 efi_lines = self.load_collect_file(
54 if (os.path.exists(sys_efi)):53 "ls_sys_firmware_efi").strip().split('\n')
55 with open(sys_efi) as fp:54
56 efi_lines = fp.read().strip().split('\n')55 # sys/firmware/efi contents differ based on kernel and configuration
57 self.assertEqual(sorted(sys_efi_expected),56 for efi_line in efi_lines:
58 sorted(efi_lines))57 self.assertIn(efi_line, sys_efi_possible)
5958
60 def test_disk_block_sizes(self):59 def test_disk_block_sizes(self):
61 """ Test disk logical and physical block size are match60 """ Test disk logical and physical block size are match
62 the class block size.61 the class block size.
63 """62 """
64 for bs in ['lbs', 'pbs']:63 for bs in ['lbs', 'pbs']:
65 with open(os.path.join(self.td.collect,64 size = int(self.load_collect_file('vda_' + bs))
66 'vda_' + bs), 'r') as fp:65 self.assertEqual(self.disk_block_size, size)
67 size = int(fp.read())
68 self.assertEqual(self.disk_block_size, size)
6966
70 def test_disk_block_size_with_blockdev(self):67 def test_disk_block_size_with_blockdev(self):
71 """ validate maas setting68 """ validate maas setting
@@ -75,10 +72,8 @@
75 --getbsz get blocksize72 --getbsz get blocksize
76 """73 """
77 for syscall in ['getss', 'getpbsz']:74 for syscall in ['getss', 'getpbsz']:
78 with open(os.path.join(self.td.collect,75 size = int(self.load_collect_file('vda_blockdev_' + syscall))
79 'vda_blockdev_' + syscall), 'r') as fp:76 self.assertEqual(self.disk_block_size, size)
80 size = int(fp.read())
81 self.assertEqual(self.disk_block_size, size)
8277
8378
84class PreciseUefiTestBasic(relbase.precise, TestBasicAbs):79class PreciseUefiTestBasic(relbase.precise, TestBasicAbs):

Subscribers

People subscribed via source and target branches