Merge lp:~smoser/curtin/add-arm64-support into lp:~newell-jensen/curtin/add-arm64-support

Proposed by Scott Moser
Status: Merged
Approved by: Newell Jensen
Approved revision: 153
Merged at revision: 147
Proposed branch: lp:~smoser/curtin/add-arm64-support
Merge into: lp:~newell-jensen/curtin/add-arm64-support
Diff against target: 551 lines (+406/-31)
4 files modified
curtin/commands/block_meta.py (+40/-22)
curtin/net/__init__.py (+114/-1)
helpers/common (+8/-8)
tests/unittests/test_net.py (+244/-0)
To merge this branch: bzr merge lp:~smoser/curtin/add-arm64-support
Reviewer Review Type Date Requested Status
Newell Jensen Approve
Review via email: mp+227619@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Need to fix merge conflict.

review: Needs Fixing
lp:~smoser/curtin/add-arm64-support updated
153. By Scott Moser

merge with Newell's branch

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

fixed merge conflicts.

Revision history for this message
Newell Jensen (newell-jensen) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'curtin/commands/block_meta.py'
2--- curtin/commands/block_meta.py 2014-07-17 17:39:59 +0000
3+++ curtin/commands/block_meta.py 2014-07-21 19:24:27 +0000
4@@ -23,8 +23,9 @@
5 from . import populate_one_subcmd
6
7 import os
8-import tempfile
9
10+SIMPLE = 'simple'
11+SIMPLE_BOOT = 'simple-boot'
12
13 CMD_ARGUMENTS = (
14 ((('-D', '--devices'),
15@@ -34,16 +35,15 @@
16 'choices': ['ext4', 'ext3'], 'default': 'ext4'}),
17 ('--boot-fstype', {'help': 'boot partition filesystem type',
18 'choices': ['ext4', 'ext3'], 'default': None}),
19- ('mode', {'help': 'meta-mode to use', 'choices': ['raid0', 'simple', 'simple-boot']}),
20+ ('mode', {'help': 'meta-mode to use',
21+ 'choices': ['raid0', SIMPLE, SIMPLE_BOOT]}),
22 )
23 )
24
25
26 def block_meta(args):
27 # main entry point for the block-meta command.
28- if args.mode == "simple":
29- meta_simple(args)
30- elif args.mode == "simple-boot":
31+ if args.mode in (SIMPLE, SIMPLE_BOOT):
32 meta_simple(args)
33 else:
34 raise NotImplementedError("mode=%s is not implemenbed" % args.mode)
35@@ -68,6 +68,25 @@
36 return block.get_root_device([devname, ])
37
38
39+def get_bootpt_cfg(cfg, enabled=False, fstype=None):
40+ # 'cfg' looks like:
41+ # enabled: boolean
42+ # fstype: filesystem type (default to 'fstype')
43+ # label: filesystem label (default to 'boot')
44+ # size: filesystem size in M (default to 512)
45+ # parm enable can enable, but not disable
46+ # parm fstype overrides cfg['fstype']
47+ ret = {'enabled': False, 'fstype': None, 'size': 512, 'label': 'boot'}
48+ ret.update(cfg)
49+ if enabled:
50+ ret['enabled'] = True
51+ if ret['enabled']:
52+ if fstype and not ret['fstype']:
53+ ret['fstype'] = fstype
54+ ret['size'] = int(ret['size'])
55+ return ret
56+
57+
58 def meta_simple(args):
59 """Creates a root partition. If args.mode == 'simple-boot', it will also
60 create a separate /boot partition.
61@@ -80,6 +99,10 @@
62 if devices is None:
63 devices = cfg.get('block-meta', {}).get('devices', [])
64
65+ bootpt = get_bootpt_cfg(
66+ cfg.get('block-meta', {}).get('boot-partition', {}),
67+ enabled=bool(args.mode is SIMPLE_BOOT), fstype=args.boot_fstype)
68+
69 # Remove duplicates but maintain ordering.
70 devices = list(OrderedDict.fromkeys(devices))
71
72@@ -110,7 +133,6 @@
73 sources = cfg.get('sources', {})
74 dd_images = util.get_dd_images(sources)
75
76- # dd images are only for Windows, not Linux
77 if len(dd_images):
78 # we have at least one dd-able image
79 # we will only take the first one
80@@ -123,10 +145,9 @@
81 logtime(
82 "partition --format uefi %s" % devnode,
83 util.subp, ("partition", "--format", "uefi", devnode))
84- elif args.mode == 'simple-boot':
85- logtime(
86- "partition %s" % devnode,
87- util.subp, ("partition", "--boot", devnode))
88+ elif bootpt['enabled']:
89+ logtime("partition %s" % devnode,
90+ util.subp, ("partition", "--boot", devnode))
91 bootdev = devnode + "1"
92 rootdev = devnode + "2"
93 else:
94@@ -140,25 +161,22 @@
95 logtime(' '.join(cmd), util.subp, cmd)
96 util.subp(['mount', rootdev, state['target']])
97
98- if args.mode == 'simple-boot':
99+ if bootpt['enabled']:
100 # create 'boot' directory in state['target']
101 boot_dir = os.path.join(state['target'], 'boot')
102 util.subp(['mkdir', boot_dir])
103 # mkfs for boot partition and mount
104- if args.boot_fstype:
105- cmd = ['mkfs.%s' % args.boot_fstype, '-q', '-L', 'cloudimg-bootfs', bootdev]
106- else:
107- cmd = ['mkfs.%s' % args.fstype, '-q', '-L', 'cloudimg-bootfs', bootdev]
108+ cmd = ['mkfs.%s' % bootpt['fstype'],
109+ '-q', '-L', bootpt['label'], bootdev]
110 logtime(' '.join(cmd), util.subp, cmd)
111 util.subp(['mount', bootdev, boot_dir])
112-
113+
114 with open(state['fstab'], "w") as fp:
115- if args.mode == 'simple-boot':
116- if args.boot_fstype:
117- fp.write("LABEL=%s /boot %s defaults 0 0\n" % ('cloudimg-bootfs', args.boot_fstype))
118- else:
119- fp.write("LABEL=%s /boot %s defaults 0 0\n" % ('cloudimg-bootfs', args.fstype))
120- fp.write("LABEL=%s / %s defaults 0 0\n" % ('cloudimg-rootfs', args.fstype))
121+ if bootpt['enabled']:
122+ fp.write("LABEL=%s /boot %s defaults 0 0\n" %
123+ (bootpt['label'], bootpt['fstype']))
124+ fp.write("LABEL=%s / %s defaults 0 0\n" %
125+ ('cloudimg-rootfs', args.fstype))
126
127 return 0
128
129
130=== modified file 'curtin/net/__init__.py'
131--- curtin/net/__init__.py 2014-03-25 23:13:46 +0000
132+++ curtin/net/__init__.py 2014-07-21 19:24:27 +0000
133@@ -1,6 +1,7 @@
134-# Copyright (C) 2013 Canonical Ltd.
135+# Copyright (C) 2013-2014 Canonical Ltd.
136 #
137 # Author: Scott Moser <scott.moser@canonical.com>
138+# Author: Blake Rouse <blake.rouse@canonical.com>
139 #
140 # Curtin is free software: you can redistribute it and/or modify it under
141 # the terms of the GNU Affero General Public License as published by the
142@@ -22,6 +23,22 @@
143
144 SYS_CLASS_NET = "/sys/class/net/"
145
146+NET_CONFIG_OPTIONS = [
147+ "address", "netmask", "broadcast", "network", "metric", "gateway",
148+ "pointtopoint", "media", "mtu", "hostname", "leasehours", "leasetime",
149+ "vendor", "client", "bootfile", "server", "hwaddr", "provider", "frame",
150+ "netnum", "endpoint", "local", "ttl",
151+ ]
152+
153+NET_CONFIG_COMMANDS = [
154+ "pre-up", "up", "post-up", "down", "pre-down", "post-down",
155+ ]
156+
157+NET_CONFIG_BRIDGE_OPTIONS = [
158+ "bridge_ageing", "bridge_bridgeprio", "bridge_fd", "bridge_gcinit",
159+ "bridge_hello", "bridge_maxage", "bridge_maxwait", "bridge_stp",
160+ ]
161+
162
163 def sys_dev_path(devname, path=""):
164 return SYS_CLASS_NET + devname + "/" + path
165@@ -95,4 +112,100 @@
166 return os.listdir(SYS_CLASS_NET)
167
168
169+class ParserError(Exception):
170+ """Raised when parser has issue parsing the interfaces file."""
171+
172+
173+def parse_deb_config_data(ifaces, contents, path):
174+ """Parses the file contents, placing result into ifaces.
175+
176+ :param ifaces: interface dictionary
177+ :param contents: contents of interfaces file
178+ :param path: directory interfaces file was located
179+ """
180+ currif = None
181+ src_dir = path
182+ for line in contents.splitlines():
183+ line = line.strip()
184+ if line.startswith('#'):
185+ continue
186+ split = line.split(' ')
187+ option = split[0]
188+ if option == "source-directory":
189+ src_dir = os.path.join(path, split[1])
190+ elif option == "source":
191+ src_path = os.path.join(src_dir, split[1])
192+ with open(src_path, "r") as fp:
193+ src_data = fp.read().strip()
194+ parse_deb_config_data(
195+ ifaces, src_data,
196+ os.path.dirname(os.path.abspath(src_path)))
197+ elif option == "auto":
198+ for iface in split[1:]:
199+ if iface not in ifaces:
200+ ifaces[iface] = {}
201+ ifaces[iface]['auto'] = True
202+ elif option == "iface":
203+ iface, family, method = split[1:4]
204+ if iface not in ifaces:
205+ ifaces[iface] = {}
206+ elif 'family' in ifaces[iface]:
207+ raise ParserError("Cannot define %s interface again.")
208+ ifaces[iface]['family'] = family
209+ ifaces[iface]['method'] = method
210+ currif = iface
211+ elif option == "hwaddress":
212+ ifaces[currif]['hwaddress'] = split[1]
213+ elif option in NET_CONFIG_OPTIONS:
214+ ifaces[currif][option] = split[1]
215+ elif option in NET_CONFIG_COMMANDS:
216+ if option not in ifaces[currif]:
217+ ifaces[currif][option] = []
218+ ifaces[currif][option].append(' '.join(split[1:]))
219+ elif option.startswith('dns-'):
220+ if 'dns' not in ifaces[currif]:
221+ ifaces[currif]['dns'] = {}
222+ if option == 'dns-search':
223+ ifaces[currif]['dns']['search'] = []
224+ for domain in split[1:]:
225+ ifaces[currif]['dns']['search'].append(domain)
226+ elif option == 'dns-nameservers':
227+ ifaces[currif]['dns']['nameservers'] = []
228+ for server in split[1:]:
229+ ifaces[currif]['dns']['nameservers'].append(server)
230+ elif option.startswith('bridge_'):
231+ if 'bridge' not in ifaces[currif]:
232+ ifaces[currif]['bridge'] = {}
233+ if option in NET_CONFIG_BRIDGE_OPTIONS:
234+ bridge_option = option.replace('bridge_', '')
235+ ifaces[currif]['bridge'][bridge_option] = split[1]
236+ elif option == "bridge_ports":
237+ ifaces[currif]['bridge']['ports'] = []
238+ for iface in split[1:]:
239+ ifaces[currif]['bridge']['ports'].append(iface)
240+ elif option == "bridge_hw" and split[1].lower() == "mac":
241+ ifaces[currif]['bridge']['mac'] = split[2]
242+ elif option == "bridge_pathcost":
243+ if 'pathcost' not in ifaces[currif]['bridge']:
244+ ifaces[currif]['bridge']['pathcost'] = {}
245+ ifaces[currif]['bridge']['pathcost'][split[1]] = split[2]
246+ elif option == "bridge_portprio":
247+ if 'portprio' not in ifaces[currif]['bridge']:
248+ ifaces[currif]['bridge']['portprio'] = {}
249+ ifaces[currif]['bridge']['portprio'][split[1]] = split[2]
250+ for iface in ifaces.keys():
251+ if 'auto' not in ifaces[iface]:
252+ ifaces[iface]['auto'] = False
253+
254+
255+def parse_deb_config(path):
256+ """Parses a debian network configuration file."""
257+ ifaces = {}
258+ with open(path, "r") as fp:
259+ contents = fp.read().strip()
260+ parse_deb_config_data(
261+ ifaces, contents,
262+ os.path.dirname(os.path.abspath(path)))
263+ return ifaces
264+
265 # vi: ts=4 expandtab syntax=python
266
267=== modified file 'helpers/common'
268--- helpers/common 2014-07-21 17:57:28 +0000
269+++ helpers/common 2014-07-21 19:24:27 +0000
270@@ -76,10 +76,10 @@
271 # This is currently the default size we have for the /boot partition
272 if [ "$boot" -ge 1 ]; then
273 maxend=$((($size/512)-$start-$bootsize))
274- if [ $maxend -lt 0 ]; then
275- { error "Disk is not big enough for /boot partition on $target";
276- return 1; }
277- fi
278+ if [ $maxend -lt 0 ]; then
279+ error "Disk is not big enough for /boot partition on $target";
280+ return 1;
281+ fi
282 else
283 maxend=$((($size/512)-$start))
284 fi
285@@ -89,13 +89,13 @@
286 [ -b "$target" ] && isblk=true
287
288 if [ "$boot" -ge 1 ]; then
289- # Creating 'efi', '/boot' and '/' partitions
290+ # Creating 'efi', '/boot' and '/' partitions
291 sgdisk --new "15:$start:+1M" --typecode=15:ef02 \
292- --new "1:+1M:+513M" --typecode=1:8300 \
293- --new "2::$end" --typecode=2:8300 "$target" ||
294+ --new "1:+1M:+513M" --typecode=1:8300 \
295+ --new "2::$end" --typecode=2:8300 "$target" ||
296 { error "failed to gpt partition $target"; return 1; }
297 else
298- # Creating 'efi' and '/' partitions
299+ # Creating 'efi' and '/' partitions
300 sgdisk --new "15:2048:+1M" --typecode=15:ef02 \
301 --new "1::$end" --typecode=1:8300 "$target" ||
302 { error "failed to gpt partition $target"; return 1; }
303
304=== added file 'tests/unittests/test_net.py'
305--- tests/unittests/test_net.py 1970-01-01 00:00:00 +0000
306+++ tests/unittests/test_net.py 2014-07-21 19:24:27 +0000
307@@ -0,0 +1,244 @@
308+from unittest import TestCase
309+import os
310+import shutil
311+import tempfile
312+
313+from curtin import net
314+from textwrap import dedent
315+
316+
317+class TestNetParserData(TestCase):
318+
319+ def test_parse_deb_config_data_ignores_comments(self):
320+ contents = dedent("""\
321+ # ignore
322+ # iface eth0 inet static
323+ # address 192.168.1.1
324+ """)
325+ ifaces = {}
326+ net.parse_deb_config_data(ifaces, contents, '')
327+ self.assertEqual({}, ifaces)
328+
329+ def test_parse_deb_config_data_basic(self):
330+ contents = dedent("""\
331+ iface eth0 inet static
332+ address 192.168.1.2
333+ netmask 255.255.255.0
334+ hwaddress aa:bb:cc:dd:ee:ff
335+ """)
336+ ifaces = {}
337+ net.parse_deb_config_data(ifaces, contents, '')
338+ self.assertEqual({
339+ 'eth0': {
340+ 'auto': False,
341+ 'family': 'inet',
342+ 'method': 'static',
343+ 'address': '192.168.1.2',
344+ 'netmask': '255.255.255.0',
345+ 'hwaddress': 'aa:bb:cc:dd:ee:ff',
346+ },
347+ }, ifaces)
348+
349+ def test_parse_deb_config_data_auto(self):
350+ contents = dedent("""\
351+ auto eth0 eth1
352+ iface eth0 inet manual
353+ iface eth1 inet manual
354+ """)
355+ ifaces = {}
356+ net.parse_deb_config_data(ifaces, contents, '')
357+ self.assertEqual({
358+ 'eth0': {
359+ 'auto': True,
360+ 'family': 'inet',
361+ 'method': 'manual',
362+ },
363+ 'eth1': {
364+ 'auto': True,
365+ 'family': 'inet',
366+ 'method': 'manual',
367+ },
368+ }, ifaces)
369+
370+ def test_parse_deb_config_data_error_on_redefine(self):
371+ contents = dedent("""\
372+ iface eth0 inet static
373+ address 192.168.1.2
374+ iface eth0 inet static
375+ address 192.168.1.3
376+ """)
377+ ifaces = {}
378+ self.assertRaises(
379+ net.ParserError,
380+ net.parse_deb_config_data, ifaces, contents, '')
381+
382+ def test_parse_deb_config_data_commands(self):
383+ contents = dedent("""\
384+ iface eth0 inet manual
385+ pre-up preup1
386+ pre-up preup2
387+ up up1
388+ post-up postup1
389+ pre-down predown1
390+ down down1
391+ down down2
392+ post-down postdown1
393+ """)
394+ ifaces = {}
395+ net.parse_deb_config_data(ifaces, contents, '')
396+ self.assertEqual({
397+ 'eth0': {
398+ 'auto': False,
399+ 'family': 'inet',
400+ 'method': 'manual',
401+ 'pre-up': ['preup1', 'preup2'],
402+ 'up': ['up1'],
403+ 'post-up': ['postup1'],
404+ 'pre-down': ['predown1'],
405+ 'down': ['down1', 'down2'],
406+ 'post-down': ['postdown1'],
407+ },
408+ }, ifaces)
409+
410+ def test_parse_deb_config_data_dns(self):
411+ contents = dedent("""\
412+ iface eth0 inet static
413+ dns-nameservers 192.168.1.1 192.168.1.2
414+ dns-search curtin local
415+ """)
416+ ifaces = {}
417+ net.parse_deb_config_data(ifaces, contents, '')
418+ self.assertEqual({
419+ 'eth0': {
420+ 'auto': False,
421+ 'family': 'inet',
422+ 'method': 'static',
423+ 'dns': {
424+ 'nameservers': ['192.168.1.1', '192.168.1.2'],
425+ 'search': ['curtin', 'local'],
426+ },
427+ },
428+ }, ifaces)
429+
430+ def test_parse_deb_config_data_bridge(self):
431+ contents = dedent("""\
432+ iface eth0 inet manual
433+ iface eth1 inet manual
434+ iface br0 inet static
435+ address 192.168.1.1
436+ netmask 255.255.255.0
437+ bridge_maxwait 30
438+ bridge_ports eth0 eth1
439+ bridge_pathcost eth0 1
440+ bridge_pathcost eth1 2
441+ bridge_portprio eth0 0
442+ bridge_portprio eth1 1
443+ """)
444+ ifaces = {}
445+ net.parse_deb_config_data(ifaces, contents, '')
446+ self.assertEqual({
447+ 'eth0': {
448+ 'auto': False,
449+ 'family': 'inet',
450+ 'method': 'manual',
451+ },
452+ 'eth1': {
453+ 'auto': False,
454+ 'family': 'inet',
455+ 'method': 'manual',
456+ },
457+ 'br0': {
458+ 'auto': False,
459+ 'family': 'inet',
460+ 'method': 'static',
461+ 'address': '192.168.1.1',
462+ 'netmask': '255.255.255.0',
463+ 'bridge': {
464+ 'maxwait': '30',
465+ 'ports': ['eth0', 'eth1'],
466+ 'pathcost': {
467+ 'eth0': '1',
468+ 'eth1': '2',
469+ },
470+ 'portprio': {
471+ 'eth0': '0',
472+ 'eth1': '1'
473+ },
474+ },
475+ },
476+ }, ifaces)
477+
478+
479+class TestNetParser(TestCase):
480+
481+ def setUp(self):
482+ self.target = tempfile.mkdtemp()
483+
484+ def tearDown(self):
485+ shutil.rmtree(self.target)
486+
487+ def make_config(self, path=None, name=None, contents=None,
488+ parse=True):
489+ if path is None:
490+ path = self.target
491+ if name is None:
492+ name = 'interfaces'
493+ path = os.path.join(path, name)
494+ if contents is None:
495+ contents = dedent("""\
496+ auto eth0
497+ iface eth0 inet static
498+ address 192.168.1.2
499+ netmask 255.255.255.0
500+ hwaddress aa:bb:cc:dd:ee:ff
501+ """)
502+ with open(path, 'w') as stream:
503+ stream.write(contents)
504+ ifaces = None
505+ if parse:
506+ ifaces = {}
507+ net.parse_deb_config_data(ifaces, contents, '')
508+ return path, ifaces
509+
510+ def test_parse_deb_config(self):
511+ path, data = self.make_config()
512+ expected = net.parse_deb_config(path)
513+ self.assertEqual(data, expected)
514+
515+ def test_parse_deb_config_source(self):
516+ path, data = self.make_config(name='interfaces2')
517+ contents = dedent("""\
518+ source interfaces2
519+ iface eth1 inet manual
520+ """)
521+ i_path, _ = self.make_config(
522+ contents=contents, parse=False)
523+ data['eth1'] = {
524+ 'auto': False,
525+ 'family': 'inet',
526+ 'method': 'manual',
527+ }
528+ expected = net.parse_deb_config(i_path)
529+ self.assertEqual(data, expected)
530+
531+ def test_parse_deb_config_source_dir(self):
532+ subdir = os.path.join(self.target, 'interfaces.d')
533+ os.mkdir(subdir)
534+ path, data = self.make_config(
535+ path=subdir, name='interfaces2')
536+ contents = dedent("""\
537+ source-directory interfaces.d
538+ source interfaces2
539+ iface eth1 inet manual
540+ """)
541+ i_path, _ = self.make_config(
542+ contents=contents, parse=False)
543+ data['eth1'] = {
544+ 'auto': False,
545+ 'family': 'inet',
546+ 'method': 'manual',
547+ }
548+ expected = net.parse_deb_config(i_path)
549+ self.assertEqual(data, expected)
550+
551+# vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches

to all changes: