1 import pvl.dhcp.config |
1 import pvl.dhcp.config |
2 import pvl.hosts.host |
2 import pvl.hosts.host |
|
3 |
|
4 def parse_dhcp_boot(boot): |
|
5 """ |
|
6 Parse the dhcp boot=... option |
|
7 |
|
8 >>> print parse_dhcp_boot(None) |
|
9 {} |
|
10 >>> print parse_dhcp_boot({'filename': '/foo'}) |
|
11 {'filename': '/foo'} |
|
12 >>> print parse_dhcp_boot({'filename': '/foo', 'next-server': 'bar'}) |
|
13 {'next-server': 'bar', 'filename': '/foo'} |
|
14 >>> print parse_dhcp_boot('/foo') |
|
15 {'filename': '/foo'} |
|
16 >>> print parse_dhcp_boot('bar:/foo') |
|
17 {'next-server': 'bar', 'filename': '/foo'} |
|
18 >>> print parse_dhcp_boot('bar:') |
|
19 {'next-server': 'bar'} |
|
20 >>> print parse_dhcp_boot('foo') |
|
21 Traceback (most recent call last): |
|
22 ... |
|
23 ValueError: invalid boot=foo |
|
24 """ |
|
25 |
|
26 # unpack dict, or str |
|
27 if not boot: |
|
28 filename = next_server = None |
|
29 boot_str = None |
|
30 |
|
31 elif isinstance(boot, dict): |
|
32 filename = boot.pop('filename', None) |
|
33 next_server = boot.pop('next-server', None) |
|
34 boot_str = boot.pop(None, None) |
|
35 |
|
36 else: |
|
37 filename = next_server = None |
|
38 boot_str = boot |
|
39 boot = None |
|
40 |
|
41 if boot: |
|
42 raise ValueError("Invalid boot.*: {instances}".format(instances=' '.join(boot))) |
|
43 |
|
44 # any boot= given overrides boot.* fields |
|
45 if not boot_str: |
|
46 pass |
|
47 |
|
48 elif boot_str.startswith('/'): |
|
49 filename = boot_str |
|
50 |
|
51 elif boot_str.endswith(':'): |
|
52 next_server = boot_str[:-1] |
|
53 |
|
54 elif ':' in boot_str: |
|
55 next_server, filename = boot_str.split(':', 1) |
|
56 |
|
57 else : |
|
58 raise ValueError("invalid boot={boot}".format(boot=boot_str)) |
|
59 |
|
60 return next_server, filename |
|
61 |
|
62 @pvl.hosts.host.register_extension |
|
63 class HostDHCP(pvl.hosts.host.HostExtension): |
|
64 EXTENSION = 'dhcp' |
|
65 EXTENSION_FIELDS = ( |
|
66 'boot', |
|
67 ) |
|
68 |
|
69 @classmethod |
|
70 def build (cls, |
|
71 boot = None, |
|
72 subclass = None, |
|
73 ): |
|
74 next_server, filename = parse_dhcp_boot(boot) |
|
75 |
|
76 return cls( |
|
77 filename = filename, |
|
78 subclass = subclass, |
|
79 next_server = next_server, |
|
80 ) |
|
81 |
|
82 def __init__(self, |
|
83 filename = None, |
|
84 next_server = None, |
|
85 subclass = None, |
|
86 ): |
|
87 self.filename = filename |
|
88 self.next_server = next_server |
|
89 self.subclass = subclass |
3 |
90 |
4 def dhcp_host_subclass (host, subclass, ethernet): |
91 def dhcp_host_subclass (host, subclass, ethernet): |
5 """ |
92 """ |
6 Build a DHCP Item for declaring a subclass for a host. |
93 Build a DHCP Item for declaring a subclass for a host. |
7 """ |
94 """ |
14 ]) |
101 ]) |
15 |
102 |
16 class HostDHCPError(pvl.hosts.host.HostError): |
103 class HostDHCPError(pvl.hosts.host.HostError): |
17 pass |
104 pass |
18 |
105 |
19 def dhcp_host_options (host, ethernet, subclass=None): |
106 def dhcp_host_options (host, ethernet, dhcp=None): |
20 """ |
107 """ |
21 Yield specific dhcp.conf host { ... } items. |
108 Yield specific dhcp.conf host { ... } items. |
22 """ |
109 """ |
23 |
110 |
24 yield 'option', 'host-name', host.name |
111 yield 'option', 'host-name', host.name |
25 yield 'hardware', 'ethernet', pvl.dhcp.config.Field(ethernet) |
112 yield 'hardware', 'ethernet', pvl.dhcp.config.Field(ethernet) |
26 |
113 |
27 if host.ip4: |
114 if host.ip4: |
28 yield 'fixed-address', pvl.dhcp.config.Field(str(host.ip4)) |
115 yield 'fixed-address', pvl.dhcp.config.Field(str(host.ip4)) |
29 |
116 |
30 for bootopt in ('next-server', 'filename'): |
117 if dhcp: |
31 if bootopt in host.boot: |
118 if dhcp.next_server: |
32 yield bootopt, host.boot[bootopt] |
119 yield 'next-server', dhcp.next_server |
33 |
120 |
34 def dhcp_host (host, |
121 if dhcp.filename: |
35 subclass = None, |
122 yield 'filename', dhcp.filename |
36 ): |
123 |
|
124 def dhcp_host (host): |
37 """ |
125 """ |
38 Yield pvl.dhcp.config.Block's |
126 Yield pvl.dhcp.config.Block's for given Host, with possible HostDHCP extensions. |
39 |
|
40 Takes dhcp:* extensions as keyword arguments |
|
41 |
|
42 subclass: name - generate a subclass name $ethernet for this host |
|
43 """ |
127 """ |
44 |
128 |
45 if not host.ethernet: |
129 if not host.ethernet: |
46 # nothing to be seen here |
130 # nothing to be seen here |
47 return |
131 return |
48 |
132 |
49 if host.owner : |
133 if host.owner : |
50 comment = u"Owner: {host.owner}".format(host=host) |
134 comment = u"Owner: {host.owner}".format(host=host) |
51 else: |
135 else: |
52 comment = None |
136 comment = None |
|
137 |
|
138 dhcp = host.extensions.get('dhcp') |
53 |
139 |
54 for index, ethernet in host.ethernet.iteritems() : |
140 for index, ethernet in host.ethernet.iteritems() : |
55 if index: |
141 if index: |
56 name = '{host.name}-{index}'.format(host=host, index=index) |
142 name = '{host.name}-{index}'.format(host=host, index=index) |
57 else: |
143 else: |
58 name = '{host.name}'.format(host=host) |
144 name = '{host.name}'.format(host=host) |
59 |
145 |
60 items = list(dhcp_host_options(host, ethernet)) |
146 items = list(dhcp_host_options(host, ethernet, dhcp=dhcp)) |
61 |
147 |
62 yield pvl.dhcp.config.Block(('host', name), items, comment=comment) |
148 yield pvl.dhcp.config.Block(('host', name), items, comment=comment) |
63 |
149 |
64 if subclass: |
150 if dhcp and dhcp.subclass: |
65 yield dhcp_host_subclass(host, subclass, ethernet) |
151 yield dhcp_host_subclass(host, dhcp.subclass, ethernet) |
66 |
152 |
67 def dhcp_hosts (hosts): |
153 def dhcp_hosts (hosts): |
68 """ |
154 """ |
69 |
155 |
70 Verifies that there are no dupliate hosts. |
156 Verifies that there are no dupliate hosts. |
71 """ |
157 """ |
72 |
158 |
73 blocks = { } |
159 blocks = { } |
74 |
160 |
75 for host in hosts: |
161 for host in hosts: |
76 extensions = host.extensions.get('dhcp', {}) |
162 for block in dhcp_host(host): |
77 |
|
78 for block in dhcp_host(host, **extensions): |
|
79 if not block.key: |
163 if not block.key: |
80 # TODO: check for unique Item-Blocks |
164 # TODO: check for unique Item-Blocks |
81 pass |
165 pass |
82 elif block.key in blocks: |
166 elif block.key in blocks: |
83 raise HostDHCPError(host, "dhcp {block} conflict with {other}; hosts on multiple networks must use unique ethernet.XXX=... naming".format(block=block, other=blocks[block.key])) |
167 raise HostDHCPError(host, "dhcp {block} conflict with {other}; hosts on multiple networks must use unique ethernet.XXX=... naming".format(block=block, other=blocks[block.key])) |