Merge lp:~rvb/maas/bug-1070765-hostname-1.2 into lp:maas/1.2
- bug-1070765-hostname-1.2
- Merge into 1.2
Proposed by
Raphaël Badin
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Raphaël Badin | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 1280 | ||||
Proposed branch: | lp:~rvb/maas/bug-1070765-hostname-1.2 | ||||
Merge into: | lp:maas/1.2 | ||||
Diff against target: |
507 lines (+236/-31) 11 files modified
src/maasserver/api.py (+19/-2) src/maasserver/forms.py (+14/-1) src/maasserver/models/dhcplease.py (+1/-5) src/maasserver/models/node.py (+25/-2) src/maasserver/models/nodegroup.py (+7/-5) src/maasserver/tests/test_api.py (+107/-0) src/maasserver/tests/test_dhcplease.py (+0/-15) src/maasserver/tests/test_forms.py (+16/-1) src/maasserver/tests/test_node.py (+26/-0) src/maasserver/utils/__init__.py (+6/-0) src/maasserver/utils/tests/test_utils.py (+15/-0) |
||||
To merge this branch: | bzr merge lp:~rvb/maas/bug-1070765-hostname-1.2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+132139@code.launchpad.net |
Commit message
Backport [r1316..r1318].
Description of the change
Backport [r1316..r1318].
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/api.py' | |||
2 | --- src/maasserver/api.py 2012-10-30 10:25:55 +0000 | |||
3 | +++ src/maasserver/api.py 2012-10-30 15:17:24 +0000 | |||
4 | @@ -165,6 +165,7 @@ | |||
5 | 165 | from maasserver.utils import ( | 165 | from maasserver.utils import ( |
6 | 166 | absolute_reverse, | 166 | absolute_reverse, |
7 | 167 | map_enum, | 167 | map_enum, |
8 | 168 | strip_domain, | ||
9 | 168 | ) | 169 | ) |
10 | 169 | from maasserver.utils.orm import get_one | 170 | from maasserver.utils.orm import get_one |
11 | 170 | from piston.handler import ( | 171 | from piston.handler import ( |
12 | @@ -474,6 +475,12 @@ | |||
13 | 474 | model = Node | 475 | model = Node |
14 | 475 | fields = DISPLAYED_NODE_FIELDS | 476 | fields = DISPLAYED_NODE_FIELDS |
15 | 476 | 477 | ||
16 | 478 | # Override the 'hostname' field so that it returns the FQDN instead as | ||
17 | 479 | # this is used by Juju to reach that node. | ||
18 | 480 | @classmethod | ||
19 | 481 | def hostname(handler, node): | ||
20 | 482 | return node.fqdn | ||
21 | 483 | |||
22 | 477 | def read(self, request, system_id): | 484 | def read(self, request, system_id): |
23 | 478 | """Read a specific Node.""" | 485 | """Read a specific Node.""" |
24 | 479 | return Node.objects.get_node_or_404( | 486 | return Node.objects.get_node_or_404( |
25 | @@ -647,8 +654,15 @@ | |||
26 | 647 | class AnonNodesHandler(AnonymousOperationsHandler): | 654 | class AnonNodesHandler(AnonymousOperationsHandler): |
27 | 648 | """Create Nodes.""" | 655 | """Create Nodes.""" |
28 | 649 | create = read = update = delete = None | 656 | create = read = update = delete = None |
29 | 657 | model = Node | ||
30 | 650 | fields = DISPLAYED_NODE_FIELDS | 658 | fields = DISPLAYED_NODE_FIELDS |
31 | 651 | 659 | ||
32 | 660 | # Override the 'hostname' field so that it returns the FQDN instead as | ||
33 | 661 | # this is used by Juju to reach that node. | ||
34 | 662 | @classmethod | ||
35 | 663 | def hostname(handler, node): | ||
36 | 664 | return node.fqdn | ||
37 | 665 | |||
38 | 652 | @operation(idempotent=False) | 666 | @operation(idempotent=False) |
39 | 653 | def new(self, request): | 667 | def new(self, request): |
40 | 654 | """Create a new Node. | 668 | """Create a new Node. |
41 | @@ -827,9 +841,12 @@ | |||
42 | 827 | request.user, NODE_PERMISSION.VIEW, ids=match_ids) | 841 | request.user, NODE_PERMISSION.VIEW, ids=match_ids) |
43 | 828 | if match_macs is not None: | 842 | if match_macs is not None: |
44 | 829 | nodes = nodes.filter(macaddress__mac_address__in=match_macs) | 843 | nodes = nodes.filter(macaddress__mac_address__in=match_macs) |
46 | 830 | # Prefetch related macaddresses and tags. | 844 | # Prefetch related macaddresses, tags and nodegroups (plus |
47 | 845 | # related interfaces). | ||
48 | 831 | nodes = nodes.prefetch_related('macaddress_set__node') | 846 | nodes = nodes.prefetch_related('macaddress_set__node') |
49 | 832 | nodes = nodes.prefetch_related('tags') | 847 | nodes = nodes.prefetch_related('tags') |
50 | 848 | nodes = nodes.prefetch_related('nodegroup') | ||
51 | 849 | nodes = nodes.prefetch_related('nodegroup__nodegroupinterface_set') | ||
52 | 833 | return nodes.order_by('id') | 850 | return nodes.order_by('id') |
53 | 834 | 851 | ||
54 | 835 | @operation(idempotent=True) | 852 | @operation(idempotent=True) |
55 | @@ -1708,7 +1725,7 @@ | |||
56 | 1708 | preseed_url = compose_preseed_url(node) | 1725 | preseed_url = compose_preseed_url(node) |
57 | 1709 | # The node's hostname may include a domain, but we ignore that | 1726 | # The node's hostname may include a domain, but we ignore that |
58 | 1710 | # and use the one from the nodegroup instead. | 1727 | # and use the one from the nodegroup instead. |
60 | 1711 | hostname = node.hostname.split('.', 1)[0] | 1728 | hostname = strip_domain(node.hostname) |
61 | 1712 | domain = node.nodegroup.name | 1729 | domain = node.nodegroup.name |
62 | 1713 | else: | 1730 | else: |
63 | 1714 | try: | 1731 | try: |
64 | 1715 | 1732 | ||
65 | === modified file 'src/maasserver/forms.py' | |||
66 | --- src/maasserver/forms.py 2012-10-30 11:29:20 +0000 | |||
67 | +++ src/maasserver/forms.py 2012-10-30 15:17:24 +0000 | |||
68 | @@ -31,6 +31,7 @@ | |||
69 | 31 | 31 | ||
70 | 32 | import collections | 32 | import collections |
71 | 33 | import json | 33 | import json |
72 | 34 | import re | ||
73 | 34 | 35 | ||
74 | 35 | from django import forms | 36 | from django import forms |
75 | 36 | from django.contrib import messages | 37 | from django.contrib import messages |
76 | @@ -82,6 +83,7 @@ | |||
77 | 82 | from maasserver.models.nodegroup import NODEGROUP_CLUSTER_NAME_TEMPLATE | 83 | from maasserver.models.nodegroup import NODEGROUP_CLUSTER_NAME_TEMPLATE |
78 | 83 | from maasserver.node_action import compile_node_actions | 84 | from maasserver.node_action import compile_node_actions |
79 | 84 | from maasserver.power_parameters import POWER_TYPE_PARAMETERS | 85 | from maasserver.power_parameters import POWER_TYPE_PARAMETERS |
80 | 86 | from maasserver.utils import strip_domain | ||
81 | 85 | from provisioningserver.enum import ( | 87 | from provisioningserver.enum import ( |
82 | 86 | POWER_TYPE, | 88 | POWER_TYPE, |
83 | 87 | POWER_TYPE_CHOICES, | 89 | POWER_TYPE_CHOICES, |
84 | @@ -334,6 +336,9 @@ | |||
85 | 334 | node.nodegroup = form_value | 336 | node.nodegroup = form_value |
86 | 335 | 337 | ||
87 | 336 | 338 | ||
88 | 339 | IP_BASED_HOSTNAME_REGEXP = re.compile('\d{1,3}-\d{1,3}-\d{1,3}-\d{1,3}') | ||
89 | 340 | |||
90 | 341 | |||
91 | 337 | class WithMACAddressesMixin: | 342 | class WithMACAddressesMixin: |
92 | 338 | """A form mixin which dynamically adds a MultipleMACAddressField to the | 343 | """A form mixin which dynamically adds a MultipleMACAddressField to the |
93 | 339 | list of fields. This mixin also overrides the 'save' method to persist | 344 | list of fields. This mixin also overrides the 'save' method to persist |
94 | @@ -389,7 +394,15 @@ | |||
95 | 389 | node.save() | 394 | node.save() |
96 | 390 | for mac in self.cleaned_data['mac_addresses']: | 395 | for mac in self.cleaned_data['mac_addresses']: |
97 | 391 | node.add_mac_address(mac) | 396 | node.add_mac_address(mac) |
99 | 392 | if self.cleaned_data['hostname'] == "": | 397 | hostname = self.cleaned_data['hostname'] |
100 | 398 | stripped_hostname = strip_domain(hostname) | ||
101 | 399 | # Generate a hostname for this node if the provided hostname is | ||
102 | 400 | # IP-based (because this means that this name comes from a DNS | ||
103 | 401 | # reverse query to the MAAS DNS) or an empty string. | ||
104 | 402 | generate_hostname = ( | ||
105 | 403 | hostname == "" or | ||
106 | 404 | IP_BASED_HOSTNAME_REGEXP.match(stripped_hostname) != None) | ||
107 | 405 | if generate_hostname: | ||
108 | 393 | node.set_mac_based_hostname(self.cleaned_data['mac_addresses'][0]) | 406 | node.set_mac_based_hostname(self.cleaned_data['mac_addresses'][0]) |
109 | 394 | return node | 407 | return node |
110 | 395 | 408 | ||
111 | 396 | 409 | ||
112 | === modified file 'src/maasserver/models/dhcplease.py' | |||
113 | --- src/maasserver/models/dhcplease.py 2012-10-09 10:30:10 +0000 | |||
114 | +++ src/maasserver/models/dhcplease.py 2012-10-30 15:17:24 +0000 | |||
115 | @@ -25,11 +25,7 @@ | |||
116 | 25 | from maasserver import DefaultMeta | 25 | from maasserver import DefaultMeta |
117 | 26 | from maasserver.fields import MACAddressField | 26 | from maasserver.fields import MACAddressField |
118 | 27 | from maasserver.models.cleansave import CleanSave | 27 | from maasserver.models.cleansave import CleanSave |
124 | 28 | 28 | from maasserver.utils import strip_domain | |
120 | 29 | |||
121 | 30 | def strip_domain(hostname): | ||
122 | 31 | """Return `hostname` with the domain part removed.""" | ||
123 | 32 | return hostname.split('.', 1)[0] | ||
125 | 33 | 29 | ||
126 | 34 | 30 | ||
127 | 35 | class DHCPLeaseManager(Manager): | 31 | class DHCPLeaseManager(Manager): |
128 | 36 | 32 | ||
129 | === modified file 'src/maasserver/models/node.py' | |||
130 | --- src/maasserver/models/node.py 2012-10-26 10:20:19 +0000 | |||
131 | +++ src/maasserver/models/node.py 2012-10-30 15:17:24 +0000 | |||
132 | @@ -63,7 +63,10 @@ | |||
133 | 63 | from maasserver.models.dhcplease import DHCPLease | 63 | from maasserver.models.dhcplease import DHCPLease |
134 | 64 | from maasserver.models.tag import Tag | 64 | from maasserver.models.tag import Tag |
135 | 65 | from maasserver.models.timestampedmodel import TimestampedModel | 65 | from maasserver.models.timestampedmodel import TimestampedModel |
137 | 66 | from maasserver.utils import get_db_state | 66 | from maasserver.utils import ( |
138 | 67 | get_db_state, | ||
139 | 68 | strip_domain, | ||
140 | 69 | ) | ||
141 | 67 | from maasserver.utils.orm import get_first | 70 | from maasserver.utils.orm import get_first |
142 | 68 | from piston.models import Token | 71 | from piston.models import Token |
143 | 69 | from provisioningserver.enum import ( | 72 | from provisioningserver.enum import ( |
144 | @@ -487,10 +490,30 @@ | |||
145 | 487 | 490 | ||
146 | 488 | def __unicode__(self): | 491 | def __unicode__(self): |
147 | 489 | if self.hostname: | 492 | if self.hostname: |
149 | 490 | return "%s (%s)" % (self.system_id, self.hostname) | 493 | return "%s (%s)" % (self.system_id, self.fqdn) |
150 | 491 | else: | 494 | else: |
151 | 492 | return self.system_id | 495 | return self.system_id |
152 | 493 | 496 | ||
153 | 497 | @property | ||
154 | 498 | def fqdn(self): | ||
155 | 499 | """Fully qualified domain name for this node. | ||
156 | 500 | |||
157 | 501 | If MAAS manages DNS for this node, the domain part of the | ||
158 | 502 | hostname (if present), is replaced by the domain configured | ||
159 | 503 | on the cluster controller. | ||
160 | 504 | If not, simply return the node's hostname. | ||
161 | 505 | """ | ||
162 | 506 | # Avoid circular imports. | ||
163 | 507 | from maasserver.dns import is_dns_managed | ||
164 | 508 | if is_dns_managed(self.nodegroup): | ||
165 | 509 | # If the hostname field contains a domain, strip it. | ||
166 | 510 | hostname = strip_domain(self.hostname) | ||
167 | 511 | # Build the FQDN by using the hostname and nodegroup.name | ||
168 | 512 | # as the domain name. | ||
169 | 513 | return '%s.%s' % (hostname, self.nodegroup.name) | ||
170 | 514 | else: | ||
171 | 515 | return self.hostname | ||
172 | 516 | |||
173 | 494 | def tag_names(self): | 517 | def tag_names(self): |
174 | 495 | # We don't use self.tags.values_list here because this does not | 518 | # We don't use self.tags.values_list here because this does not |
175 | 496 | # take advantage of the cache. | 519 | # take advantage of the cache. |
176 | 497 | 520 | ||
177 | === modified file 'src/maasserver/models/nodegroup.py' | |||
178 | --- src/maasserver/models/nodegroup.py 2012-10-11 10:59:40 +0000 | |||
179 | +++ src/maasserver/models/nodegroup.py 2012-10-30 15:17:24 +0000 | |||
180 | @@ -31,7 +31,6 @@ | |||
181 | 31 | from maasserver.models.nodegroupinterface import NodeGroupInterface | 31 | from maasserver.models.nodegroupinterface import NodeGroupInterface |
182 | 32 | from maasserver.models.timestampedmodel import TimestampedModel | 32 | from maasserver.models.timestampedmodel import TimestampedModel |
183 | 33 | from maasserver.refresh_worker import refresh_worker | 33 | from maasserver.refresh_worker import refresh_worker |
184 | 34 | from maasserver.utils.orm import get_one | ||
185 | 35 | from piston.models import ( | 34 | from piston.models import ( |
186 | 36 | KEY_SIZE, | 35 | KEY_SIZE, |
187 | 37 | Token, | 36 | Token, |
188 | @@ -196,10 +195,13 @@ | |||
189 | 196 | This is a temporary method that should be refactored once we add | 195 | This is a temporary method that should be refactored once we add |
190 | 197 | proper support for multiple interfaces on a nodegroup. | 196 | proper support for multiple interfaces on a nodegroup. |
191 | 198 | """ | 197 | """ |
196 | 199 | return get_one( | 198 | # Iterate over all the interfaces in python instead of doing the |
197 | 200 | NodeGroupInterface.objects.filter( | 199 | # filtering in SQL so that this will use the cached version of |
198 | 201 | nodegroup=self).exclude( | 200 | # self.nodegroupinterface_set if it is there. |
199 | 202 | management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)) | 201 | for interface in self.nodegroupinterface_set.all(): |
200 | 202 | if interface.management != NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED: | ||
201 | 203 | return interface | ||
202 | 204 | return None | ||
203 | 203 | 205 | ||
204 | 204 | def ensure_dhcp_key(self): | 206 | def ensure_dhcp_key(self): |
205 | 205 | """Ensure that this nodegroup has a dhcp key. | 207 | """Ensure that this nodegroup has a dhcp key. |
206 | 206 | 208 | ||
207 | === modified file 'src/maasserver/tests/test_api.py' | |||
208 | --- src/maasserver/tests/test_api.py 2012-10-30 11:00:20 +0000 | |||
209 | +++ src/maasserver/tests/test_api.py 2012-10-30 15:17:24 +0000 | |||
210 | @@ -381,6 +381,25 @@ | |||
211 | 381 | [diane] = Node.objects.filter(hostname='diane') | 381 | [diane] = Node.objects.filter(hostname='diane') |
212 | 382 | self.assertEqual(architecture, diane.architecture) | 382 | self.assertEqual(architecture, diane.architecture) |
213 | 383 | 383 | ||
214 | 384 | def test_POST_new_generates_hostname_if_ip_based_hostname(self): | ||
215 | 385 | hostname = '192-168-5-19.domain' | ||
216 | 386 | response = self.client.post( | ||
217 | 387 | self.get_uri('nodes/'), | ||
218 | 388 | { | ||
219 | 389 | 'op': 'new', | ||
220 | 390 | 'hostname': hostname, | ||
221 | 391 | 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES), | ||
222 | 392 | 'after_commissioning_action': | ||
223 | 393 | NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, | ||
224 | 394 | 'mac_addresses': [factory.getRandomMACAddress()], | ||
225 | 395 | }) | ||
226 | 396 | parsed_result = json.loads(response.content) | ||
227 | 397 | |||
228 | 398 | self.assertEqual(httplib.OK, response.status_code) | ||
229 | 399 | system_id = parsed_result.get('system_id') | ||
230 | 400 | node = Node.objects.get(system_id=system_id) | ||
231 | 401 | self.assertNotEqual(hostname, node.hostname) | ||
232 | 402 | |||
233 | 384 | def test_POST_new_creates_node_with_power_parameters(self): | 403 | def test_POST_new_creates_node_with_power_parameters(self): |
234 | 385 | # We're setting power parameters so we disable start_commissioning to | 404 | # We're setting power parameters so we disable start_commissioning to |
235 | 386 | # prevent anything from attempting to issue power instructions. | 405 | # prevent anything from attempting to issue power instructions. |
236 | @@ -618,6 +637,93 @@ | |||
237 | 618 | self.assertItemsEqual(['architecture'], parsed_result) | 637 | self.assertItemsEqual(['architecture'], parsed_result) |
238 | 619 | 638 | ||
239 | 620 | 639 | ||
240 | 640 | class NodeHostnameTest(APIv10TestMixin, MultipleUsersScenarios, TestCase): | ||
241 | 641 | |||
242 | 642 | scenarios = [ | ||
243 | 643 | ('user', dict(userfactory=factory.make_user)), | ||
244 | 644 | ('admin', dict(userfactory=factory.make_admin)), | ||
245 | 645 | ] | ||
246 | 646 | |||
247 | 647 | def test_GET_list_returns_fqdn_with_domain_name_from_cluster(self): | ||
248 | 648 | # If DNS management is enabled, the domain part of a hostname | ||
249 | 649 | # is replaced by the domain name defined on the cluster. | ||
250 | 650 | hostname_without_domain = factory.make_name('hostname') | ||
251 | 651 | hostname_with_domain = '%s.%s' % ( | ||
252 | 652 | hostname_without_domain, factory.getRandomString()) | ||
253 | 653 | domain = factory.make_name('domain') | ||
254 | 654 | nodegroup = factory.make_node_group( | ||
255 | 655 | status=NODEGROUP_STATUS.ACCEPTED, | ||
256 | 656 | name=domain, | ||
257 | 657 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) | ||
258 | 658 | node = factory.make_node( | ||
259 | 659 | hostname=hostname_with_domain, nodegroup=nodegroup) | ||
260 | 660 | expected_hostname = '%s.%s' % (hostname_without_domain, domain) | ||
261 | 661 | response = self.client.get(self.get_uri('nodes/'), {'op': 'list'}) | ||
262 | 662 | self.assertEqual(httplib.OK, response.status_code, response.content) | ||
263 | 663 | parsed_result = json.loads(response.content) | ||
264 | 664 | self.assertItemsEqual( | ||
265 | 665 | [expected_hostname], | ||
266 | 666 | [node.get('hostname') for node in parsed_result]) | ||
267 | 667 | |||
268 | 668 | |||
269 | 669 | class NodeHostnameEnlistmentTest(APIv10TestMixin, MultipleUsersScenarios, | ||
270 | 670 | TestCase): | ||
271 | 671 | |||
272 | 672 | scenarios = [ | ||
273 | 673 | ('anon', dict(userfactory=lambda: AnonymousUser())), | ||
274 | 674 | ('user', dict(userfactory=factory.make_user)), | ||
275 | 675 | ('admin', dict(userfactory=factory.make_admin)), | ||
276 | 676 | ] | ||
277 | 677 | |||
278 | 678 | def test_created_node_has_domain_from_cluster(self): | ||
279 | 679 | hostname_without_domain = factory.make_name('hostname') | ||
280 | 680 | hostname_with_domain = '%s.%s' % ( | ||
281 | 681 | hostname_without_domain, factory.getRandomString()) | ||
282 | 682 | domain = factory.make_name('domain') | ||
283 | 683 | factory.make_node_group( | ||
284 | 684 | status=NODEGROUP_STATUS.ACCEPTED, | ||
285 | 685 | name=domain, | ||
286 | 686 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) | ||
287 | 687 | response = self.client.post( | ||
288 | 688 | self.get_uri('nodes/'), | ||
289 | 689 | { | ||
290 | 690 | 'op': 'new', | ||
291 | 691 | 'hostname': hostname_with_domain, | ||
292 | 692 | 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES), | ||
293 | 693 | 'after_commissioning_action': | ||
294 | 694 | NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, | ||
295 | 695 | 'mac_addresses': [factory.getRandomMACAddress()], | ||
296 | 696 | }) | ||
297 | 697 | self.assertEqual(httplib.OK, response.status_code, response.content) | ||
298 | 698 | parsed_result = json.loads(response.content) | ||
299 | 699 | expected_hostname = '%s.%s' % (hostname_without_domain, domain) | ||
300 | 700 | self.assertEqual( | ||
301 | 701 | expected_hostname, parsed_result.get('hostname')) | ||
302 | 702 | |||
303 | 703 | def test_created_node_gets_domain_from_cluster_appended(self): | ||
304 | 704 | hostname_without_domain = factory.make_name('hostname') | ||
305 | 705 | domain = factory.make_name('domain') | ||
306 | 706 | factory.make_node_group( | ||
307 | 707 | status=NODEGROUP_STATUS.ACCEPTED, | ||
308 | 708 | name=domain, | ||
309 | 709 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) | ||
310 | 710 | response = self.client.post( | ||
311 | 711 | self.get_uri('nodes/'), | ||
312 | 712 | { | ||
313 | 713 | 'op': 'new', | ||
314 | 714 | 'hostname': hostname_without_domain, | ||
315 | 715 | 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES), | ||
316 | 716 | 'after_commissioning_action': | ||
317 | 717 | NODE_AFTER_COMMISSIONING_ACTION.DEFAULT, | ||
318 | 718 | 'mac_addresses': [factory.getRandomMACAddress()], | ||
319 | 719 | }) | ||
320 | 720 | self.assertEqual(httplib.OK, response.status_code, response.content) | ||
321 | 721 | parsed_result = json.loads(response.content) | ||
322 | 722 | expected_hostname = '%s.%s' % (hostname_without_domain, domain) | ||
323 | 723 | self.assertEqual( | ||
324 | 724 | expected_hostname, parsed_result.get('hostname')) | ||
325 | 725 | |||
326 | 726 | |||
327 | 621 | class NonAdminEnlistmentAPITest(APIv10TestMixin, MultipleUsersScenarios, | 727 | class NonAdminEnlistmentAPITest(APIv10TestMixin, MultipleUsersScenarios, |
328 | 622 | TestCase): | 728 | TestCase): |
329 | 623 | # Enlistment tests for non-admin users. | 729 | # Enlistment tests for non-admin users. |
330 | @@ -684,6 +790,7 @@ | |||
331 | 684 | 'netboot', | 790 | 'netboot', |
332 | 685 | 'power_type', | 791 | 'power_type', |
333 | 686 | 'tag_names', | 792 | 'tag_names', |
334 | 793 | 'resource_uri', | ||
335 | 687 | ], | 794 | ], |
336 | 688 | list(parsed_result)) | 795 | list(parsed_result)) |
337 | 689 | 796 | ||
338 | 690 | 797 | ||
339 | === modified file 'src/maasserver/tests/test_dhcplease.py' | |||
340 | --- src/maasserver/tests/test_dhcplease.py 2012-10-09 10:30:10 +0000 | |||
341 | +++ src/maasserver/tests/test_dhcplease.py 2012-10-30 15:17:24 +0000 | |||
342 | @@ -14,7 +14,6 @@ | |||
343 | 14 | 14 | ||
344 | 15 | from maasserver import dns | 15 | from maasserver import dns |
345 | 16 | from maasserver.models import DHCPLease | 16 | from maasserver.models import DHCPLease |
346 | 17 | from maasserver.models.dhcplease import strip_domain | ||
347 | 18 | from maasserver.testing.factory import factory | 17 | from maasserver.testing.factory import factory |
348 | 19 | from maasserver.testing.testcase import TestCase | 18 | from maasserver.testing.testcase import TestCase |
349 | 20 | from maasserver.utils import ignore_unused | 19 | from maasserver.utils import ignore_unused |
350 | @@ -47,20 +46,6 @@ | |||
351 | 47 | self.assertEqual(mac, lease.mac) | 46 | self.assertEqual(mac, lease.mac) |
352 | 48 | 47 | ||
353 | 49 | 48 | ||
354 | 50 | class TestUtitilies(TestCase): | ||
355 | 51 | |||
356 | 52 | def test_strip_domain(self): | ||
357 | 53 | input_and_results = [ | ||
358 | 54 | ('name.domain', 'name'), | ||
359 | 55 | ('name', 'name'), | ||
360 | 56 | ('name.domain.what', 'name'), | ||
361 | 57 | ('name..domain', 'name'), | ||
362 | 58 | ] | ||
363 | 59 | inputs = [input for input, _ in input_and_results] | ||
364 | 60 | results = [result for _, result in input_and_results] | ||
365 | 61 | self.assertEqual(results, map(strip_domain, inputs)) | ||
366 | 62 | |||
367 | 63 | |||
368 | 64 | class TestDHCPLeaseManager(TestCase): | 49 | class TestDHCPLeaseManager(TestCase): |
369 | 65 | """Tests for :class:`DHCPLeaseManager`.""" | 50 | """Tests for :class:`DHCPLeaseManager`.""" |
370 | 66 | 51 | ||
371 | 67 | 52 | ||
372 | === modified file 'src/maasserver/tests/test_forms.py' | |||
373 | --- src/maasserver/tests/test_forms.py 2012-10-30 10:46:58 +0000 | |||
374 | +++ src/maasserver/tests/test_forms.py 2012-10-30 15:17:24 +0000 | |||
375 | @@ -112,14 +112,17 @@ | |||
376 | 112 | return query_dict | 112 | return query_dict |
377 | 113 | 113 | ||
378 | 114 | def make_params(self, mac_addresses=None, architecture=None, | 114 | def make_params(self, mac_addresses=None, architecture=None, |
380 | 115 | nodegroup=None): | 115 | hostname=None, nodegroup=None): |
381 | 116 | if mac_addresses is None: | 116 | if mac_addresses is None: |
382 | 117 | mac_addresses = [factory.getRandomMACAddress()] | 117 | mac_addresses = [factory.getRandomMACAddress()] |
383 | 118 | if architecture is None: | 118 | if architecture is None: |
384 | 119 | architecture = factory.getRandomEnum(ARCHITECTURE) | 119 | architecture = factory.getRandomEnum(ARCHITECTURE) |
385 | 120 | if hostname is None: | ||
386 | 121 | hostname = factory.make_name('hostname') | ||
387 | 120 | params = { | 122 | params = { |
388 | 121 | 'mac_addresses': mac_addresses, | 123 | 'mac_addresses': mac_addresses, |
389 | 122 | 'architecture': architecture, | 124 | 'architecture': architecture, |
390 | 125 | 'hostname': hostname, | ||
391 | 123 | } | 126 | } |
392 | 124 | if nodegroup is not None: | 127 | if nodegroup is not None: |
393 | 125 | params['nodegroup'] = nodegroup | 128 | params['nodegroup'] = nodegroup |
394 | @@ -218,6 +221,18 @@ | |||
395 | 218 | form.save() | 221 | form.save() |
396 | 219 | self.assertEqual(original_nodegroup, reload_object(node).nodegroup) | 222 | self.assertEqual(original_nodegroup, reload_object(node).nodegroup) |
397 | 220 | 223 | ||
398 | 224 | def test_form_without_hostname_generates_hostname(self): | ||
399 | 225 | form = NodeWithMACAddressesForm(self.make_params(hostname='')) | ||
400 | 226 | node = form.save() | ||
401 | 227 | self.assertTrue(len(node.hostname) > 0) | ||
402 | 228 | |||
403 | 229 | def test_form_with_ip_based_hostname_generates_hostname(self): | ||
404 | 230 | ip_based_hostname = '192-168-12-10.domain' | ||
405 | 231 | form = NodeWithMACAddressesForm( | ||
406 | 232 | self.make_params(hostname=ip_based_hostname)) | ||
407 | 233 | node = form.save() | ||
408 | 234 | self.assertNotEqual(ip_based_hostname, node.hostname) | ||
409 | 235 | |||
410 | 221 | 236 | ||
411 | 222 | class TestOptionForm(ConfigForm): | 237 | class TestOptionForm(ConfigForm): |
412 | 223 | field1 = forms.CharField(label="Field 1", max_length=10) | 238 | field1 = forms.CharField(label="Field 1", max_length=10) |
413 | 224 | 239 | ||
414 | === modified file 'src/maasserver/tests/test_node.py' | |||
415 | --- src/maasserver/tests/test_node.py 2012-10-24 14:59:57 +0000 | |||
416 | +++ src/maasserver/tests/test_node.py 2012-10-30 15:17:24 +0000 | |||
417 | @@ -26,6 +26,8 @@ | |||
418 | 26 | NODE_STATUS, | 26 | NODE_STATUS, |
419 | 27 | NODE_STATUS_CHOICES, | 27 | NODE_STATUS_CHOICES, |
420 | 28 | NODE_STATUS_CHOICES_DICT, | 28 | NODE_STATUS_CHOICES_DICT, |
421 | 29 | NODEGROUP_STATUS, | ||
422 | 30 | NODEGROUPINTERFACE_MANAGEMENT, | ||
423 | 29 | ) | 31 | ) |
424 | 30 | from maasserver.exceptions import NodeStateViolation | 32 | from maasserver.exceptions import NodeStateViolation |
425 | 31 | from maasserver.models import ( | 33 | from maasserver.models import ( |
426 | @@ -570,6 +572,30 @@ | |||
427 | 570 | node = reload_object(node) | 572 | node = reload_object(node) |
428 | 571 | self.assertEqual([], list(node.tags.all())) | 573 | self.assertEqual([], list(node.tags.all())) |
429 | 572 | 574 | ||
430 | 575 | def test_fqdn_returns_hostname_if_dns_not_managed(self): | ||
431 | 576 | nodegroup = factory.make_node_group( | ||
432 | 577 | name=factory.getRandomString(), | ||
433 | 578 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP) | ||
434 | 579 | hostname_with_domain = '%s.%s' % ( | ||
435 | 580 | factory.getRandomString(), factory.getRandomString()) | ||
436 | 581 | node = factory.make_node( | ||
437 | 582 | nodegroup=nodegroup, hostname=hostname_with_domain) | ||
438 | 583 | self.assertEqual(hostname_with_domain, node.fqdn) | ||
439 | 584 | |||
440 | 585 | def test_fqdn_replaces_hostname_if_dns_is_managed(self): | ||
441 | 586 | hostname_without_domain = factory.make_name('hostname') | ||
442 | 587 | hostname_with_domain = '%s.%s' % ( | ||
443 | 588 | hostname_without_domain, factory.getRandomString()) | ||
444 | 589 | domain = factory.make_name('domain') | ||
445 | 590 | nodegroup = factory.make_node_group( | ||
446 | 591 | status=NODEGROUP_STATUS.ACCEPTED, | ||
447 | 592 | name=domain, | ||
448 | 593 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) | ||
449 | 594 | node = factory.make_node( | ||
450 | 595 | hostname=hostname_with_domain, nodegroup=nodegroup) | ||
451 | 596 | expected_hostname = '%s.%s' % (hostname_without_domain, domain) | ||
452 | 597 | self.assertEqual(expected_hostname, node.fqdn) | ||
453 | 598 | |||
454 | 573 | 599 | ||
455 | 574 | class NodeTransitionsTests(TestCase): | 600 | class NodeTransitionsTests(TestCase): |
456 | 575 | """Test the structure of NODE_TRANSITIONS.""" | 601 | """Test the structure of NODE_TRANSITIONS.""" |
457 | 576 | 602 | ||
458 | === modified file 'src/maasserver/utils/__init__.py' | |||
459 | --- src/maasserver/utils/__init__.py 2012-08-24 10:28:29 +0000 | |||
460 | +++ src/maasserver/utils/__init__.py 2012-10-30 15:17:24 +0000 | |||
461 | @@ -15,6 +15,7 @@ | |||
462 | 15 | 'get_db_state', | 15 | 'get_db_state', |
463 | 16 | 'ignore_unused', | 16 | 'ignore_unused', |
464 | 17 | 'map_enum', | 17 | 'map_enum', |
465 | 18 | 'strip_domain', | ||
466 | 18 | ] | 19 | ] |
467 | 19 | 20 | ||
468 | 20 | from urllib import urlencode | 21 | from urllib import urlencode |
469 | @@ -82,3 +83,8 @@ | |||
470 | 82 | if query is not None: | 83 | if query is not None: |
471 | 83 | url += '?%s' % urlencode(query, doseq=True) | 84 | url += '?%s' % urlencode(query, doseq=True) |
472 | 84 | return url | 85 | return url |
473 | 86 | |||
474 | 87 | |||
475 | 88 | def strip_domain(hostname): | ||
476 | 89 | """Return `hostname` with the domain part removed.""" | ||
477 | 90 | return hostname.split('.', 1)[0] | ||
478 | 85 | 91 | ||
479 | === modified file 'src/maasserver/utils/tests/test_utils.py' | |||
480 | --- src/maasserver/utils/tests/test_utils.py 2012-06-26 16:36:10 +0000 | |||
481 | +++ src/maasserver/utils/tests/test_utils.py 2012-10-30 15:17:24 +0000 | |||
482 | @@ -23,6 +23,7 @@ | |||
483 | 23 | absolute_reverse, | 23 | absolute_reverse, |
484 | 24 | get_db_state, | 24 | get_db_state, |
485 | 25 | map_enum, | 25 | map_enum, |
486 | 26 | strip_domain, | ||
487 | 26 | ) | 27 | ) |
488 | 27 | from maastesting.testcase import TestCase | 28 | from maastesting.testcase import TestCase |
489 | 28 | 29 | ||
490 | @@ -104,3 +105,17 @@ | |||
491 | 104 | NODE_STATUS_CHOICES, but_not=[status]) | 105 | NODE_STATUS_CHOICES, but_not=[status]) |
492 | 105 | node.status = another_status | 106 | node.status = another_status |
493 | 106 | self.assertEqual(status, get_db_state(node, 'status')) | 107 | self.assertEqual(status, get_db_state(node, 'status')) |
494 | 108 | |||
495 | 109 | |||
496 | 110 | class TestStripDomain(TestCase): | ||
497 | 111 | |||
498 | 112 | def test_strip_domain(self): | ||
499 | 113 | input_and_results = [ | ||
500 | 114 | ('name.domain', 'name'), | ||
501 | 115 | ('name', 'name'), | ||
502 | 116 | ('name.domain.what', 'name'), | ||
503 | 117 | ('name..domain', 'name'), | ||
504 | 118 | ] | ||
505 | 119 | inputs = [input for input, _ in input_and_results] | ||
506 | 120 | results = [result for _, result in input_and_results] | ||
507 | 121 | self.assertEqual(results, map(strip_domain, inputs)) |
Self-approving this as it is a straightforward backport.