Merge lp:~lamont/maas/bug-1660188 into lp:~maas-committers/maas/trunk
- bug-1660188
- Merge into trunk
Proposed by
LaMont Jones
Status: | Rejected |
---|---|
Rejected by: | LaMont Jones |
Proposed branch: | lp:~lamont/maas/bug-1660188 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
2262 lines (+992/-336) 14 files modified
src/maasserver/enum.py (+23/-0) src/maasserver/forms/__init__.py (+24/-2) src/maasserver/forms/interface.py (+20/-0) src/maasserver/forms/tests/test_device.py (+1/-0) src/maasserver/static/js/angular/controllers/node_details.js (+36/-8) src/maasserver/static/js/angular/controllers/node_details_networking.js (+130/-44) src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+5/-3) src/maasserver/static/js/angular/factories/devices.js (+7/-4) src/maasserver/static/js/angular/factories/tests/test_devices.js (+1/-1) src/maasserver/static/partials/node-details.html (+150/-95) src/maasserver/static/partials/nodes-list.html (+3/-1) src/maasserver/websockets/handlers/device.py (+132/-45) src/maasserver/websockets/handlers/node.py (+95/-90) src/maasserver/websockets/handlers/tests/test_device.py (+365/-43) |
To merge this branch: | bzr merge lp:~lamont/maas/bug-1660188 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Needs Fixing | ||
Review via email:
|
Commit message
Create Device Details page.
Description of the change
Create Device Details page.
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gavin Panella (allenap) wrote : | # |
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
LaMont Jones (lamont) wrote : | # |
Replies.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Blake Rouse (blake-rouse) wrote : | # |
Overall almost there. Couple of issues with the directives, just always use ng-if instead of ng-show or ng-hide. ng-if is better performance as it actually removes the dom elements from the page. There has been some effort to transition all to ng-if but that has not happened 100% yet.
I did run into this issue on the devices details page. The DHCP warning shouldn't be there and I think editing should be possible for a device if a rack controller if no rack controllers. I really want to remove that limitation for machines as well, but not yet, sigh...
review:
Needs Fixing
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Blake Rouse (blake-rouse) wrote : | # |
Forgot the link to the screenshot:
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/enum.py' | |||
2 | --- src/maasserver/enum.py 2017-02-17 21:27:46 +0000 | |||
3 | +++ src/maasserver/enum.py 2017-02-28 04:16:58 +0000 | |||
4 | @@ -7,6 +7,8 @@ | |||
5 | 7 | 'CACHE_MODE_TYPE', | 7 | 'CACHE_MODE_TYPE', |
6 | 8 | 'CACHE_MODE_TYPE_CHOICES', | 8 | 'CACHE_MODE_TYPE_CHOICES', |
7 | 9 | 'COMPONENT', | 9 | 'COMPONENT', |
8 | 10 | 'DEVICE_IP_ASSIGNMENT_TYPE', | ||
9 | 11 | 'DEVICE_IP_ASSIGNMENT_TYPE_CHOICES', | ||
10 | 10 | 'FILESYSTEM_GROUP_TYPE', | 12 | 'FILESYSTEM_GROUP_TYPE', |
11 | 11 | 'FILESYSTEM_GROUP_TYPE_CHOICES', | 13 | 'FILESYSTEM_GROUP_TYPE_CHOICES', |
12 | 12 | 'FILESYSTEM_TYPE', | 14 | 'FILESYSTEM_TYPE', |
13 | @@ -206,6 +208,27 @@ | |||
14 | 206 | ADMIN = 'admin_node' | 208 | ADMIN = 'admin_node' |
15 | 207 | 209 | ||
16 | 208 | 210 | ||
17 | 211 | class DEVICE_IP_ASSIGNMENT_TYPE: | ||
18 | 212 | """The vocabulary of a `Device`'s possible IP assignment type. This value | ||
19 | 213 | is calculated by looking at the overall model for a `Device`. This is not | ||
20 | 214 | set directly on the model.""" | ||
21 | 215 | #: Device is outside of MAAS control. | ||
22 | 216 | EXTERNAL = "external" | ||
23 | 217 | |||
24 | 218 | #: Device receives ip address from `NodeGroupInterface` dynamic range. | ||
25 | 219 | DYNAMIC = "dynamic" | ||
26 | 220 | |||
27 | 221 | #: Device has ip address assigned from `NodeGroupInterface` static range. | ||
28 | 222 | STATIC = "static" | ||
29 | 223 | |||
30 | 224 | |||
31 | 225 | DEVICE_IP_ASSIGNMENT_TYPE_CHOICES = ( | ||
32 | 226 | (DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC, "Dynamic"), | ||
33 | 227 | (DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL, "External"), | ||
34 | 228 | (DEVICE_IP_ASSIGNMENT_TYPE.STATIC, "Static"), | ||
35 | 229 | ) | ||
36 | 230 | |||
37 | 231 | |||
38 | 209 | class PRESEED_TYPE: | 232 | class PRESEED_TYPE: |
39 | 210 | """Types of preseed documents that can be generated.""" | 233 | """Types of preseed documents that can be generated.""" |
40 | 211 | COMMISSIONING = 'commissioning' | 234 | COMMISSIONING = 'commissioning' |
41 | 212 | 235 | ||
42 | === modified file 'src/maasserver/forms/__init__.py' | |||
43 | --- src/maasserver/forms/__init__.py 2017-02-17 14:23:04 +0000 | |||
44 | +++ src/maasserver/forms/__init__.py 2017-02-28 04:16:58 +0000 | |||
45 | @@ -803,27 +803,42 @@ | |||
46 | 803 | required=False, initial=None, | 803 | required=False, initial=None, |
47 | 804 | queryset=Node.objects.all(), to_field_name='system_id') | 804 | queryset=Node.objects.all(), to_field_name='system_id') |
48 | 805 | 805 | ||
49 | 806 | zone = forms.ModelChoiceField( | ||
50 | 807 | label="Physical zone", required=False, | ||
51 | 808 | initial=Zone.objects.get_default_zone, | ||
52 | 809 | queryset=Zone.objects.all(), to_field_name='name') | ||
53 | 810 | |||
54 | 806 | class Meta: | 811 | class Meta: |
55 | 807 | model = Device | 812 | model = Device |
56 | 808 | 813 | ||
57 | 809 | fields = NodeForm.Meta.fields + ( | 814 | fields = NodeForm.Meta.fields + ( |
58 | 810 | 'parent', | 815 | 'parent', |
59 | 816 | 'zone', | ||
60 | 811 | ) | 817 | ) |
61 | 812 | 818 | ||
62 | 819 | zone = forms.ModelChoiceField( | ||
63 | 820 | label="Physical zone", required=False, | ||
64 | 821 | initial=Zone.objects.get_default_zone, | ||
65 | 822 | queryset=Zone.objects.all(), to_field_name='name') | ||
66 | 823 | |||
67 | 813 | def __init__(self, request=None, *args, **kwargs): | 824 | def __init__(self, request=None, *args, **kwargs): |
68 | 814 | super(DeviceForm, self).__init__(*args, **kwargs) | 825 | super(DeviceForm, self).__init__(*args, **kwargs) |
69 | 815 | self.request = request | 826 | self.request = request |
70 | 816 | 827 | ||
71 | 817 | instance = kwargs.get('instance') | 828 | instance = kwargs.get('instance') |
72 | 818 | self.set_up_initial_device(instance) | 829 | self.set_up_initial_device(instance) |
73 | 830 | if instance is not None: | ||
74 | 831 | self.initial['zone'] = instance.zone.name | ||
75 | 819 | 832 | ||
76 | 820 | def set_up_initial_device(self, instance): | 833 | def set_up_initial_device(self, instance): |
77 | 821 | """Initialize the 'parent' field if a device instance was given. | 834 | """Initialize the 'parent' field if a device instance was given. |
78 | 822 | 835 | ||
79 | 823 | This is a workaround for Django bug #17657. | 836 | This is a workaround for Django bug #17657. |
80 | 824 | """ | 837 | """ |
83 | 825 | if instance is not None and instance.parent is not None: | 838 | if instance is not None: |
84 | 826 | self.initial['parent'] = instance.parent.system_id | 839 | if instance.parent is not None: |
85 | 840 | self.initial['parent'] = instance.parent.system_id | ||
86 | 841 | self.initial['zone'] = instance.zone.name | ||
87 | 827 | 842 | ||
88 | 828 | def save(self, commit=True): | 843 | def save(self, commit=True): |
89 | 829 | device = super(DeviceForm, self).save(commit=False) | 844 | device = super(DeviceForm, self).save(commit=False) |
90 | @@ -832,6 +847,10 @@ | |||
91 | 832 | # Set the owner: devices are owned by their creator. | 847 | # Set the owner: devices are owned by their creator. |
92 | 833 | device.owner = self.request.user | 848 | device.owner = self.request.user |
93 | 834 | 849 | ||
94 | 850 | zone = self.cleaned_data.get('zone') | ||
95 | 851 | if zone: | ||
96 | 852 | device.zone = zone | ||
97 | 853 | |||
98 | 835 | # If the device has a parent and no domain was provided, | 854 | # If the device has a parent and no domain was provided, |
99 | 836 | # inherit the parent's domain. | 855 | # inherit the parent's domain. |
100 | 837 | if device.parent: | 856 | if device.parent: |
101 | @@ -839,6 +858,9 @@ | |||
102 | 839 | device.parent.domain): | 858 | device.parent.domain): |
103 | 840 | device.domain = device.parent.domain | 859 | device.domain = device.parent.domain |
104 | 841 | 860 | ||
105 | 861 | zone = self.cleaned_data.get('zone') | ||
106 | 862 | if zone: | ||
107 | 863 | device.zone = zone | ||
108 | 842 | device.save() | 864 | device.save() |
109 | 843 | return device | 865 | return device |
110 | 844 | 866 | ||
111 | 845 | 867 | ||
112 | === modified file 'src/maasserver/forms/interface.py' | |||
113 | --- src/maasserver/forms/interface.py 2017-02-17 14:23:04 +0000 | |||
114 | +++ src/maasserver/forms/interface.py 2017-02-28 04:16:58 +0000 | |||
115 | @@ -16,6 +16,7 @@ | |||
116 | 16 | BOND_LACP_RATE_CHOICES, | 16 | BOND_LACP_RATE_CHOICES, |
117 | 17 | BOND_MODE_CHOICES, | 17 | BOND_MODE_CHOICES, |
118 | 18 | BOND_XMIT_HASH_POLICY_CHOICES, | 18 | BOND_XMIT_HASH_POLICY_CHOICES, |
119 | 19 | DEVICE_IP_ASSIGNMENT_TYPE, | ||
120 | 19 | INTERFACE_TYPE, | 20 | INTERFACE_TYPE, |
121 | 20 | IPADDRESS_TYPE, | 21 | IPADDRESS_TYPE, |
122 | 21 | NODE_TYPE, | 22 | NODE_TYPE, |
123 | @@ -56,6 +57,16 @@ | |||
124 | 56 | accept_ra = forms.NullBooleanField(required=False) | 57 | accept_ra = forms.NullBooleanField(required=False) |
125 | 57 | autoconf = forms.NullBooleanField(required=False) | 58 | autoconf = forms.NullBooleanField(required=False) |
126 | 58 | 59 | ||
127 | 60 | # Device parameters | ||
128 | 61 | ip_assignment = forms.MultipleChoiceField( | ||
129 | 62 | choices=( | ||
130 | 63 | ('static', DEVICE_IP_ASSIGNMENT_TYPE.STATIC), | ||
131 | 64 | ('dynamic', DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC), | ||
132 | 65 | ('external', DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL)), | ||
133 | 66 | required=False) | ||
134 | 67 | ip_address = forms.GenericIPAddressField( | ||
135 | 68 | unpack_ipv4=True, required=False) | ||
136 | 69 | |||
137 | 59 | @staticmethod | 70 | @staticmethod |
138 | 60 | def get_interface_form(type): | 71 | def get_interface_form(type): |
139 | 61 | try: | 72 | try: |
140 | @@ -148,9 +159,18 @@ | |||
141 | 148 | msg = "Parents are related to different nodes." | 159 | msg = "Parents are related to different nodes." |
142 | 149 | set_form_error(self, 'name', msg) | 160 | set_form_error(self, 'name', msg) |
143 | 150 | 161 | ||
144 | 162 | def clean_device(self, cleaned_data): | ||
145 | 163 | ip_assignment = cleaned_data.get('ip_assignment') | ||
146 | 164 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: | ||
147 | 165 | # Dynamic means that there is no IP address stored. | ||
148 | 166 | cleaned_data['ip_address'] = None | ||
149 | 167 | return cleaned_data | ||
150 | 168 | |||
151 | 151 | def clean(self): | 169 | def clean(self): |
152 | 152 | cleaned_data = super(InterfaceForm, self).clean() | 170 | cleaned_data = super(InterfaceForm, self).clean() |
153 | 153 | self.clean_parents_all_same_node(cleaned_data.get('parents')) | 171 | self.clean_parents_all_same_node(cleaned_data.get('parents')) |
154 | 172 | if self.node.node_type == NODE_TYPE.DEVICE: | ||
155 | 173 | cleaned_data = self.clean_device(cleaned_data) | ||
156 | 154 | return cleaned_data | 174 | return cleaned_data |
157 | 155 | 175 | ||
158 | 156 | def _set_param(self, interface, key): | 176 | def _set_param(self, interface, key): |
159 | 157 | 177 | ||
160 | === modified file 'src/maasserver/forms/tests/test_device.py' | |||
161 | --- src/maasserver/forms/tests/test_device.py 2017-02-17 14:23:04 +0000 | |||
162 | +++ src/maasserver/forms/tests/test_device.py 2017-02-28 04:16:58 +0000 | |||
163 | @@ -40,6 +40,7 @@ | |||
164 | 40 | 'parent', | 40 | 'parent', |
165 | 41 | 'disable_ipv4', | 41 | 'disable_ipv4', |
166 | 42 | 'swap_size', | 42 | 'swap_size', |
167 | 43 | 'zone', | ||
168 | 43 | ], list(form.fields)) | 44 | ], list(form.fields)) |
169 | 44 | 45 | ||
170 | 45 | def test_changes_device_parent(self): | 46 | def test_changes_device_parent(self): |
171 | 46 | 47 | ||
172 | === modified file 'src/maasserver/static/js/angular/controllers/node_details.js' | |||
173 | --- src/maasserver/static/js/angular/controllers/node_details.js 2017-01-28 00:51:47 +0000 | |||
174 | +++ src/maasserver/static/js/angular/controllers/node_details.js 2017-02-28 04:16:58 +0000 | |||
175 | @@ -6,14 +6,22 @@ | |||
176 | 6 | 6 | ||
177 | 7 | angular.module('MAAS').controller('NodeDetailsController', [ | 7 | angular.module('MAAS').controller('NodeDetailsController', [ |
178 | 8 | '$scope', '$rootScope', '$routeParams', '$location', '$interval', | 8 | '$scope', '$rootScope', '$routeParams', '$location', '$interval', |
179 | 9 | 'DevicesManager', | ||
180 | 9 | 'MachinesManager', 'ControllersManager', 'ZonesManager', 'GeneralManager', | 10 | 'MachinesManager', 'ControllersManager', 'ZonesManager', 'GeneralManager', |
181 | 10 | 'UsersManager', 'TagsManager', 'DomainsManager', 'ManagerHelperService', | 11 | 'UsersManager', 'TagsManager', 'DomainsManager', 'ManagerHelperService', |
182 | 11 | 'ServicesManager', 'ErrorService', 'ValidationService', function( | 12 | 'ServicesManager', 'ErrorService', 'ValidationService', function( |
184 | 12 | $scope, $rootScope, $routeParams, $location, $interval, | 13 | $scope, $rootScope, $routeParams, $location, $interval, DevicesManager, |
185 | 13 | MachinesManager, ControllersManager, ZonesManager, GeneralManager, | 14 | MachinesManager, ControllersManager, ZonesManager, GeneralManager, |
186 | 14 | UsersManager, TagsManager, DomainsManager, ManagerHelperService, | 15 | UsersManager, TagsManager, DomainsManager, ManagerHelperService, |
187 | 15 | ServicesManager, ErrorService, ValidationService) { | 16 | ServicesManager, ErrorService, ValidationService) { |
188 | 16 | 17 | ||
189 | 18 | // Mapping of device.ip_assignment to viewable text. | ||
190 | 19 | var DEVICE_IP_ASSIGNMENT = { | ||
191 | 20 | external: "External", | ||
192 | 21 | dynamic: "Dynamic", | ||
193 | 22 | "static": "Static" | ||
194 | 23 | }; | ||
195 | 24 | |||
196 | 17 | // Set title and page. | 25 | // Set title and page. |
197 | 18 | $rootScope.title = "Loading..."; | 26 | $rootScope.title = "Loading..."; |
198 | 19 | $rootScope.page = "nodes"; | 27 | $rootScope.page = "nodes"; |
199 | @@ -84,6 +92,11 @@ | |||
200 | 84 | parameters: {} | 92 | parameters: {} |
201 | 85 | }; | 93 | }; |
202 | 86 | 94 | ||
203 | 95 | // Get the display text for device ip assignment type. | ||
204 | 96 | $scope.getDeviceIPAssignment = function(ipAssignment) { | ||
205 | 97 | return DEVICE_IP_ASSIGNMENT[ipAssignment]; | ||
206 | 98 | }; | ||
207 | 99 | |||
208 | 87 | // Events section. | 100 | // Events section. |
209 | 88 | $scope.events = { | 101 | $scope.events = { |
210 | 89 | limit: 10 | 102 | limit: 10 |
211 | @@ -674,9 +687,10 @@ | |||
212 | 674 | $scope.hasInvalidArchitecture = function() { | 687 | $scope.hasInvalidArchitecture = function() { |
213 | 675 | if(angular.isObject($scope.node)) { | 688 | if(angular.isObject($scope.node)) { |
214 | 676 | return ( | 689 | return ( |
218 | 677 | $scope.node.architecture === "" || | 690 | !$scope.isDevice && ( |
219 | 678 | $scope.summary.architecture.options.indexOf( | 691 | $scope.node.architecture === "" || |
220 | 679 | $scope.node.architecture) === -1); | 692 | $scope.summary.architecture.options.indexOf( |
221 | 693 | $scope.node.architecture) === -1)); | ||
222 | 680 | } else { | 694 | } else { |
223 | 681 | return false; | 695 | return false; |
224 | 682 | } | 696 | } |
225 | @@ -685,9 +699,10 @@ | |||
226 | 685 | // Return true if the current architecture selection is invalid. | 699 | // Return true if the current architecture selection is invalid. |
227 | 686 | $scope.invalidArchitecture = function() { | 700 | $scope.invalidArchitecture = function() { |
228 | 687 | return ( | 701 | return ( |
232 | 688 | $scope.summary.architecture.selected === "" || | 702 | !$scope.isDevice && ( |
233 | 689 | $scope.summary.architecture.options.indexOf( | 703 | $scope.summary.architecture.selected === "" || |
234 | 690 | $scope.summary.architecture.selected) === -1); | 704 | $scope.summary.architecture.options.indexOf( |
235 | 705 | $scope.summary.architecture.selected) === -1)); | ||
236 | 691 | }; | 706 | }; |
237 | 692 | 707 | ||
238 | 693 | // Return true if at least a rack controller is connected to the | 708 | // Return true if at least a rack controller is connected to the |
239 | @@ -702,7 +717,7 @@ | |||
240 | 702 | return ( | 717 | return ( |
241 | 703 | $scope.isRackControllerConnected() && | 718 | $scope.isRackControllerConnected() && |
242 | 704 | $scope.isSuperUser() && | 719 | $scope.isSuperUser() && |
244 | 705 | !$scope.isController); | 720 | !$scope.isController && !$scope.isDevice); |
245 | 706 | }; | 721 | }; |
246 | 707 | 722 | ||
247 | 708 | // Called to edit the node name. | 723 | // Called to edit the node name. |
248 | @@ -1029,6 +1044,7 @@ | |||
249 | 1029 | // Load all the required managers. | 1044 | // Load all the required managers. |
250 | 1030 | ManagerHelperService.loadManagers($scope, [ | 1045 | ManagerHelperService.loadManagers($scope, [ |
251 | 1031 | MachinesManager, | 1046 | MachinesManager, |
252 | 1047 | DevicesManager, | ||
253 | 1032 | ControllersManager, | 1048 | ControllersManager, |
254 | 1033 | ZonesManager, | 1049 | ZonesManager, |
255 | 1034 | GeneralManager, | 1050 | GeneralManager, |
256 | @@ -1040,11 +1056,19 @@ | |||
257 | 1040 | if('controller' === $routeParams.type) { | 1056 | if('controller' === $routeParams.type) { |
258 | 1041 | $scope.nodesManager = ControllersManager; | 1057 | $scope.nodesManager = ControllersManager; |
259 | 1042 | $scope.isController = true; | 1058 | $scope.isController = true; |
260 | 1059 | $scope.isDevice = false; | ||
261 | 1043 | $scope.type_name = 'controller'; | 1060 | $scope.type_name = 'controller'; |
262 | 1044 | $scope.type_name_title = 'Controller'; | 1061 | $scope.type_name_title = 'Controller'; |
263 | 1062 | }else if('device' === $routeParams.type) { | ||
264 | 1063 | $scope.nodesManager = DevicesManager; | ||
265 | 1064 | $scope.isController = false; | ||
266 | 1065 | $scope.isDevice = true; | ||
267 | 1066 | $scope.type_name = 'device'; | ||
268 | 1067 | $scope.type_name_title = 'Device'; | ||
269 | 1045 | }else{ | 1068 | }else{ |
270 | 1046 | $scope.nodesManager = MachinesManager; | 1069 | $scope.nodesManager = MachinesManager; |
271 | 1047 | $scope.isController = false; | 1070 | $scope.isController = false; |
272 | 1071 | $scope.isDevice = false; | ||
273 | 1048 | $scope.type_name = 'machine'; | 1072 | $scope.type_name = 'machine'; |
274 | 1049 | $scope.type_name_title = 'Machine'; | 1073 | $scope.type_name_title = 'Machine'; |
275 | 1050 | } | 1074 | } |
276 | @@ -1062,6 +1086,10 @@ | |||
277 | 1062 | }, function(error) { | 1086 | }, function(error) { |
278 | 1063 | ErrorService.raiseError(error); | 1087 | ErrorService.raiseError(error); |
279 | 1064 | }); | 1088 | }); |
280 | 1089 | activeNode = $scope.nodesManager.getActiveItem(); | ||
281 | 1090 | } | ||
282 | 1091 | if($scope.isDevice) { | ||
283 | 1092 | $scope.ip_assignment = activeNode.ip_assignment; | ||
284 | 1065 | } | 1093 | } |
285 | 1066 | 1094 | ||
286 | 1067 | // Poll for architectures, hwe_kernels, and osinfo the whole | 1095 | // Poll for architectures, hwe_kernels, and osinfo the whole |
287 | 1068 | 1096 | ||
288 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js' | |||
289 | --- src/maasserver/static/js/angular/controllers/node_details_networking.js 2016-10-26 19:05:43 +0000 | |||
290 | +++ src/maasserver/static/js/angular/controllers/node_details_networking.js 2017-02-28 04:16:58 +0000 | |||
291 | @@ -171,6 +171,28 @@ | |||
292 | 171 | EDIT: "edit" | 171 | EDIT: "edit" |
293 | 172 | }; | 172 | }; |
294 | 173 | 173 | ||
295 | 174 | var IP_ASSIGNMENT = { | ||
296 | 175 | DYNAMIC: "dynamic", | ||
297 | 176 | EXTERNAL: "external", | ||
298 | 177 | STATIC: "static" | ||
299 | 178 | }; | ||
300 | 179 | |||
301 | 180 | // Device ip assignment options. | ||
302 | 181 | $scope.ipAssignments = [ | ||
303 | 182 | { | ||
304 | 183 | name: IP_ASSIGNMENT.EXTERNAL, | ||
305 | 184 | text: "External" | ||
306 | 185 | }, | ||
307 | 186 | { | ||
308 | 187 | name: IP_ASSIGNMENT.DYNAMIC, | ||
309 | 188 | text: "Dynamic" | ||
310 | 189 | }, | ||
311 | 190 | { | ||
312 | 191 | name: IP_ASSIGNMENT.STATIC, | ||
313 | 192 | text: "Static" | ||
314 | 193 | } | ||
315 | 194 | ]; | ||
316 | 195 | |||
317 | 174 | // Set the initial values for this scope. | 196 | // Set the initial values for this scope. |
318 | 175 | $scope.loaded = false; | 197 | $scope.loaded = false; |
319 | 176 | $scope.nodeHasLoaded = false; | 198 | $scope.nodeHasLoaded = false; |
320 | @@ -542,6 +564,10 @@ | |||
321 | 542 | // If the user is not the superuser, pretend it's not Ready. | 564 | // If the user is not the superuser, pretend it's not Ready. |
322 | 543 | return false; | 565 | return false; |
323 | 544 | } | 566 | } |
324 | 567 | if ($scope.$parent.isDevice) { | ||
325 | 568 | // Devices are never Ready, for our purposes, for now. | ||
326 | 569 | return true; | ||
327 | 570 | } | ||
328 | 545 | if ($scope.$parent.isController) { | 571 | if ($scope.$parent.isController) { |
329 | 546 | // Controllers are always Ready, for our purposes. | 572 | // Controllers are always Ready, for our purposes. |
330 | 547 | return true; | 573 | return true; |
331 | @@ -563,8 +589,8 @@ | |||
332 | 563 | // If the user is not the superuser, pretend it's not Ready. | 589 | // If the user is not the superuser, pretend it's not Ready. |
333 | 564 | return false; | 590 | return false; |
334 | 565 | } | 591 | } |
337 | 566 | if ($scope.$parent.isController) { | 592 | if ($scope.$parent.isController || $scope.$parent.isDevice) { |
338 | 567 | // Controllers never in limited mode. | 593 | // Controllers and Devices are never in limited mode. |
339 | 568 | return false; | 594 | return false; |
340 | 569 | } | 595 | } |
341 | 570 | return ( | 596 | return ( |
342 | @@ -581,9 +607,9 @@ | |||
343 | 581 | // If the user is not a superuser, disable the networking panel. | 607 | // If the user is not a superuser, disable the networking panel. |
344 | 582 | return true; | 608 | return true; |
345 | 583 | } | 609 | } |
347 | 584 | if ($scope.$parent.isController) { | 610 | if ($scope.$parent.isController || $scope.$parent.isDevice) { |
348 | 585 | // Never disable the full networking panel when its a | 611 | // Never disable the full networking panel when its a |
350 | 586 | // controller. | 612 | // Controller or Device. |
351 | 587 | return false; | 613 | return false; |
352 | 588 | } | 614 | } |
353 | 589 | if (angular.isObject($scope.node) && | 615 | if (angular.isObject($scope.node) && |
354 | @@ -772,18 +798,29 @@ | |||
355 | 772 | $scope.edit = function(nic) { | 798 | $scope.edit = function(nic) { |
356 | 773 | $scope.selectedInterfaces = [$scope.getUniqueKey(nic)]; | 799 | $scope.selectedInterfaces = [$scope.getUniqueKey(nic)]; |
357 | 774 | $scope.selectedMode = SELECTION_MODE.EDIT; | 800 | $scope.selectedMode = SELECTION_MODE.EDIT; |
370 | 775 | $scope.editInterface = { | 801 | if($scope.$parent.isDevice) { |
371 | 776 | id: nic.id, | 802 | $scope.editInterface = { |
372 | 777 | name: nic.name, | 803 | id: nic.id, |
373 | 778 | mac_address: nic.mac_address, | 804 | name: nic.name, |
374 | 779 | tags: angular.copy(nic.tags), | 805 | mac_address: nic.mac_address, |
375 | 780 | fabric: nic.fabric, | 806 | subnet: nic.subnet, |
376 | 781 | vlan: nic.vlan, | 807 | ip_address: nic.ip_address, |
377 | 782 | subnet: nic.subnet, | 808 | ip_assignment: nic.ip_assignment |
378 | 783 | mode: nic.mode, | 809 | }; |
379 | 784 | ip_address: nic.ip_address, | 810 | }else{ |
380 | 785 | link_id: nic.link_id | 811 | $scope.editInterface = { |
381 | 786 | }; | 812 | id: nic.id, |
382 | 813 | name: nic.name, | ||
383 | 814 | mac_address: nic.mac_address, | ||
384 | 815 | tags: angular.copy(nic.tags), | ||
385 | 816 | fabric: nic.fabric, | ||
386 | 817 | vlan: nic.vlan, | ||
387 | 818 | subnet: nic.subnet, | ||
388 | 819 | mode: nic.mode, | ||
389 | 820 | ip_address: nic.ip_address, | ||
390 | 821 | link_id: nic.link_id | ||
391 | 822 | }; | ||
392 | 823 | } | ||
393 | 787 | }; | 824 | }; |
394 | 788 | 825 | ||
395 | 789 | // Called when the fabric is changed. | 826 | // Called when the fabric is changed. |
396 | @@ -828,6 +865,10 @@ | |||
397 | 828 | } | 865 | } |
398 | 829 | }; | 866 | }; |
399 | 830 | 867 | ||
400 | 868 | $scope.ipAssignementChanged = function(nic) { | ||
401 | 869 | return; | ||
402 | 870 | }; | ||
403 | 871 | |||
404 | 831 | // Called to cancel edit mode. | 872 | // Called to cancel edit mode. |
405 | 832 | $scope.editCancel = function(nic) { | 873 | $scope.editCancel = function(nic) { |
406 | 833 | $scope.selectedInterfaces = []; | 874 | $scope.selectedInterfaces = []; |
407 | @@ -837,13 +878,24 @@ | |||
408 | 837 | 878 | ||
409 | 838 | // Save the following interface on the node. | 879 | // Save the following interface on the node. |
410 | 839 | $scope.saveInterface = function(nic) { | 880 | $scope.saveInterface = function(nic) { |
418 | 840 | var params = { | 881 | var params; |
419 | 841 | "name": nic.name, | 882 | if($scope.$parent.isDevice) { |
420 | 842 | "mac_address": nic.mac_address, | 883 | params = { |
421 | 843 | "tags": nic.tags.map( | 884 | "name": nic.name, |
422 | 844 | function(tag) { return tag.text; }) | 885 | "mac_address": nic.mac_address, |
423 | 845 | }; | 886 | "ip_assignment": nic.ip_assignment, |
424 | 846 | if(nic.vlan !== null) { | 887 | "ip_address": nic.ip_address, |
425 | 888 | "subnet": nic.subnet | ||
426 | 889 | }; | ||
427 | 890 | }else{ | ||
428 | 891 | params = { | ||
429 | 892 | "name": nic.name, | ||
430 | 893 | "mac_address": nic.mac_address, | ||
431 | 894 | "tags": nic.tags.map( | ||
432 | 895 | function(tag) { return tag.text; }) | ||
433 | 896 | }; | ||
434 | 897 | } | ||
435 | 898 | if(nic.vlan !== undefined && nic.vlan !== null) { | ||
436 | 847 | params.vlan = nic.vlan.id; | 899 | params.vlan = nic.vlan.id; |
437 | 848 | } else { | 900 | } else { |
438 | 849 | params.vlan = null; | 901 | params.vlan = null; |
439 | @@ -866,6 +918,9 @@ | |||
440 | 866 | var params = { | 918 | var params = { |
441 | 867 | "mode": nic.mode | 919 | "mode": nic.mode |
442 | 868 | }; | 920 | }; |
443 | 921 | if($scope.$parent.isDevice) { | ||
444 | 922 | params.ip_assignment=nic.ip_assignment; | ||
445 | 923 | } | ||
446 | 869 | if(angular.isObject(nic.subnet)) { | 924 | if(angular.isObject(nic.subnet)) { |
447 | 870 | params.subnet = nic.subnet.id; | 925 | params.subnet = nic.subnet.id; |
448 | 871 | } | 926 | } |
449 | @@ -1110,9 +1165,18 @@ | |||
450 | 1110 | 1165 | ||
451 | 1111 | // Perform the add action over the websocket. | 1166 | // Perform the add action over the websocket. |
452 | 1112 | $scope.addInterface = function(type) { | 1167 | $scope.addInterface = function(type) { |
454 | 1113 | if($scope.newInterface.type === INTERFACE_TYPE.ALIAS) { | 1168 | var nic; |
455 | 1169 | if($scope.$parent.isDevice) { | ||
456 | 1170 | nic = { | ||
457 | 1171 | id: $scope.newInterface.parent.id, | ||
458 | 1172 | ip_assignment: $scope.newInterface.ip_assignment, | ||
459 | 1173 | subnet: $scope.newInterface.subnet, | ||
460 | 1174 | ip_address: $scope.newInterface.ip_address | ||
461 | 1175 | }; | ||
462 | 1176 | $scope.saveInterfaceLink(nic); | ||
463 | 1177 | } else if($scope.newInterface.type === INTERFACE_TYPE.ALIAS) { | ||
464 | 1114 | // Add a link to the current interface. | 1178 | // Add a link to the current interface. |
466 | 1115 | var nic = { | 1179 | nic = { |
467 | 1116 | id: $scope.newInterface.parent.id, | 1180 | id: $scope.newInterface.parent.id, |
468 | 1117 | mode: $scope.newInterface.mode, | 1181 | mode: $scope.newInterface.mode, |
469 | 1118 | subnet: $scope.newInterface.subnet, | 1182 | subnet: $scope.newInterface.subnet, |
470 | @@ -1416,17 +1480,29 @@ | |||
471 | 1416 | $scope.showCreatePhysical = function() { | 1480 | $scope.showCreatePhysical = function() { |
472 | 1417 | if($scope.selectedMode === SELECTION_MODE.NONE) { | 1481 | if($scope.selectedMode === SELECTION_MODE.NONE) { |
473 | 1418 | $scope.selectedMode = SELECTION_MODE.CREATE_PHYSICAL; | 1482 | $scope.selectedMode = SELECTION_MODE.CREATE_PHYSICAL; |
485 | 1419 | $scope.newInterface = { | 1483 | if($scope.$parent.isDevice) { |
486 | 1420 | name: getNextName("eth"), | 1484 | $scope.newInterface = { |
487 | 1421 | macAddress: "", | 1485 | name: getNextName("eth"), |
488 | 1422 | macError: false, | 1486 | macAddress: "", |
489 | 1423 | tags: [], | 1487 | macError: false, |
490 | 1424 | errorMsg: null, | 1488 | tags: [], |
491 | 1425 | fabric: $scope.fabrics[0], | 1489 | errorMsg: null, |
492 | 1426 | vlan: getDefaultVLAN($scope.fabrics[0]), | 1490 | subnet: null, |
493 | 1427 | subnet: null, | 1491 | ip_assignment: IP_ASSIGNMENT.DYNAMIC |
494 | 1428 | mode: LINK_MODE.LINK_UP | 1492 | }; |
495 | 1429 | }; | 1493 | }else{ |
496 | 1494 | $scope.newInterface = { | ||
497 | 1495 | name: getNextName("eth"), | ||
498 | 1496 | macAddress: "", | ||
499 | 1497 | macError: false, | ||
500 | 1498 | tags: [], | ||
501 | 1499 | errorMsg: null, | ||
502 | 1500 | fabric: $scope.fabrics[0], | ||
503 | 1501 | vlan: getDefaultVLAN($scope.fabrics[0]), | ||
504 | 1502 | subnet: null, | ||
505 | 1503 | mode: LINK_MODE.LINK_UP | ||
506 | 1504 | }; | ||
507 | 1505 | } | ||
508 | 1430 | } | 1506 | } |
509 | 1431 | }; | 1507 | }; |
510 | 1432 | 1508 | ||
511 | @@ -1444,14 +1520,24 @@ | |||
512 | 1444 | return; | 1520 | return; |
513 | 1445 | } | 1521 | } |
514 | 1446 | 1522 | ||
523 | 1447 | var params = { | 1523 | var params; |
524 | 1448 | name: $scope.newInterface.name, | 1524 | if($scope.$parent.isDevice) { |
525 | 1449 | tags: $scope.newInterface.tags.map( | 1525 | params = { |
526 | 1450 | function(tag) { return tag.text; }), | 1526 | name: $scope.newInterface.name, |
527 | 1451 | mac_address: $scope.newInterface.macAddress, | 1527 | mac_address: $scope.newInterface.macAddress, |
528 | 1452 | vlan: $scope.newInterface.vlan.id, | 1528 | ip_assignment: $scope.newInterface.ip_assignment, |
529 | 1453 | mode: $scope.newInterface.mode | 1529 | ip_address: $scope.newInterface.ip_address |
530 | 1454 | }; | 1530 | }; |
531 | 1531 | }else{ | ||
532 | 1532 | params = { | ||
533 | 1533 | name: $scope.newInterface.name, | ||
534 | 1534 | tags: $scope.newInterface.tags.map( | ||
535 | 1535 | function(tag) { return tag.text; }), | ||
536 | 1536 | mac_address: $scope.newInterface.macAddress, | ||
537 | 1537 | vlan: $scope.newInterface.vlan.id, | ||
538 | 1538 | mode: $scope.newInterface.mode | ||
539 | 1539 | }; | ||
540 | 1540 | } | ||
541 | 1455 | if(angular.isObject($scope.newInterface.subnet)) { | 1541 | if(angular.isObject($scope.newInterface.subnet)) { |
542 | 1456 | params.subnet = $scope.newInterface.subnet.id; | 1542 | params.subnet = $scope.newInterface.subnet.id; |
543 | 1457 | } | 1543 | } |
544 | 1458 | 1544 | ||
545 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details.js' | |||
546 | --- src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2017-01-28 00:51:47 +0000 | |||
547 | +++ src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2017-02-28 04:16:58 +0000 | |||
548 | @@ -42,6 +42,7 @@ | |||
549 | 42 | var webSocket; | 42 | var webSocket; |
550 | 43 | beforeEach(inject(function($injector) { | 43 | beforeEach(inject(function($injector) { |
551 | 44 | MachinesManager = $injector.get("MachinesManager"); | 44 | MachinesManager = $injector.get("MachinesManager"); |
552 | 45 | DevicesManager = $injector.get("DevicesManager"); | ||
553 | 45 | ControllersManager = $injector.get("ControllersManager"); | 46 | ControllersManager = $injector.get("ControllersManager"); |
554 | 46 | ZonesManager = $injector.get("ZonesManager"); | 47 | ZonesManager = $injector.get("ZonesManager"); |
555 | 47 | GeneralManager = $injector.get("GeneralManager"); | 48 | GeneralManager = $injector.get("GeneralManager"); |
556 | @@ -136,6 +137,7 @@ | |||
557 | 136 | $routeParams: $routeParams, | 137 | $routeParams: $routeParams, |
558 | 137 | $location: $location, | 138 | $location: $location, |
559 | 138 | MachinesManager: MachinesManager, | 139 | MachinesManager: MachinesManager, |
560 | 140 | DevicesManager: DevicesManager, | ||
561 | 139 | ControllersManager: ControllersManager, | 141 | ControllersManager: ControllersManager, |
562 | 140 | ZonesManager: ZonesManager, | 142 | ZonesManager: ZonesManager, |
563 | 141 | GeneralManager: GeneralManager, | 143 | GeneralManager: GeneralManager, |
564 | @@ -260,9 +262,9 @@ | |||
565 | 260 | var controller = makeController(); | 262 | var controller = makeController(); |
566 | 261 | expect(ManagerHelperService.loadManagers).toHaveBeenCalledWith( | 263 | expect(ManagerHelperService.loadManagers).toHaveBeenCalledWith( |
567 | 262 | $scope, [ | 264 | $scope, [ |
571 | 263 | MachinesManager, ControllersManager, ZonesManager, | 265 | MachinesManager, DevicesManager, ControllersManager, |
572 | 264 | GeneralManager, UsersManager, TagsManager, DomainsManager, | 266 | ZonesManager, GeneralManager, UsersManager, TagsManager, |
573 | 265 | ServicesManager]); | 267 | DomainsManager, ServicesManager]); |
574 | 266 | }); | 268 | }); |
575 | 267 | 269 | ||
576 | 268 | it("doesnt call setActiveItem if node is loaded", function() { | 270 | it("doesnt call setActiveItem if node is loaded", function() { |
577 | 269 | 271 | ||
578 | === modified file 'src/maasserver/static/js/angular/factories/devices.js' | |||
579 | --- src/maasserver/static/js/angular/factories/devices.js 2016-09-27 00:47:54 +0000 | |||
580 | +++ src/maasserver/static/js/angular/factories/devices.js 2017-02-28 04:16:58 +0000 | |||
581 | @@ -11,16 +11,19 @@ | |||
582 | 11 | 11 | ||
583 | 12 | angular.module('MAAS').factory( | 12 | angular.module('MAAS').factory( |
584 | 13 | 'DevicesManager', | 13 | 'DevicesManager', |
587 | 14 | ['$q', '$rootScope', 'RegionConnection', 'Manager', function( | 14 | ['$q', '$rootScope', 'RegionConnection', 'NodesManager', function( |
588 | 15 | $q, $rootScope, RegionConnection, Manager) { | 15 | $q, $rootScope, RegionConnection, NodesManager) { |
589 | 16 | 16 | ||
590 | 17 | function DevicesManager() { | 17 | function DevicesManager() { |
592 | 18 | Manager.call(this); | 18 | NodesManager.call(this); |
593 | 19 | 19 | ||
594 | 20 | this._pk = "system_id"; | 20 | this._pk = "system_id"; |
595 | 21 | this._handler = "device"; | 21 | this._handler = "device"; |
596 | 22 | this._metadataAttributes = { | 22 | this._metadataAttributes = { |
597 | 23 | "owner": null, | 23 | "owner": null, |
598 | 24 | "subnets": null, | ||
599 | 25 | "fabrics": null, | ||
600 | 26 | "spaces": null, | ||
601 | 24 | "tags": null, | 27 | "tags": null, |
602 | 25 | "zone": function(device) { | 28 | "zone": function(device) { |
603 | 26 | return device.zone.name; | 29 | return device.zone.name; |
604 | @@ -34,7 +37,7 @@ | |||
605 | 34 | }); | 37 | }); |
606 | 35 | } | 38 | } |
607 | 36 | 39 | ||
609 | 37 | DevicesManager.prototype = new Manager(); | 40 | DevicesManager.prototype = new NodesManager(); |
610 | 38 | 41 | ||
611 | 39 | // Create a device. | 42 | // Create a device. |
612 | 40 | DevicesManager.prototype.create = function(node) { | 43 | DevicesManager.prototype.create = function(node) { |
613 | 41 | 44 | ||
614 | === modified file 'src/maasserver/static/js/angular/factories/tests/test_devices.js' | |||
615 | --- src/maasserver/static/js/angular/factories/tests/test_devices.js 2016-09-27 14:24:19 +0000 | |||
616 | +++ src/maasserver/static/js/angular/factories/tests/test_devices.js 2017-02-28 04:16:58 +0000 | |||
617 | @@ -46,7 +46,7 @@ | |||
618 | 46 | expect(DevicesManager._pk).toBe("system_id"); | 46 | expect(DevicesManager._pk).toBe("system_id"); |
619 | 47 | expect(DevicesManager._handler).toBe("device"); | 47 | expect(DevicesManager._handler).toBe("device"); |
620 | 48 | expect(Object.keys(DevicesManager._metadataAttributes)).toEqual( | 48 | expect(Object.keys(DevicesManager._metadataAttributes)).toEqual( |
622 | 49 | ["owner", "tags", "zone"]); | 49 | ["owner", "subnets", "fabrics", "spaces", "tags", "zone"]); |
623 | 50 | }); | 50 | }); |
624 | 51 | 51 | ||
625 | 52 | describe("createInferface", function() { | 52 | describe("createInferface", function() { |
626 | 53 | 53 | ||
627 | === modified file 'src/maasserver/static/partials/node-details.html' | |||
628 | --- src/maasserver/static/partials/node-details.html 2017-02-17 14:23:04 +0000 | |||
629 | +++ src/maasserver/static/partials/node-details.html 2017-02-28 04:16:58 +0000 | |||
630 | @@ -30,7 +30,7 @@ | |||
631 | 30 | data-ng-click="saveEditHeader()">Save</a> | 30 | data-ng-click="saveEditHeader()">Save</a> |
632 | 31 | 31 | ||
633 | 32 | <!-- XXX ricgard 2016-06-16 - Need to add e2e test. --> | 32 | <!-- XXX ricgard 2016-06-16 - Need to add e2e test. --> |
635 | 33 | <p class="page-header__status" data-ng-hide="isController || header.editing"> | 33 | <p class="page-header__status" data-ng-if="!isController && !isDevice && !header.editing"> |
636 | 34 | {$ node.status $} | 34 | {$ node.status $} |
637 | 35 | <span class="u-text--{$ getPowerStateClass() $} u-margin--left-small"><i class="icon icon--power-{$ getPowerStateClass() $} u-margin--right-tiny"></i>{$ getPowerStateText() $}</span> | 35 | <span class="u-text--{$ getPowerStateClass() $} u-margin--left-small"><i class="icon icon--power-{$ getPowerStateClass() $} u-margin--right-tiny"></i>{$ getPowerStateText() $}</span> |
638 | 36 | <a href="" class="page-header__status-check" data-ng-show="canCheckPowerState()" data-ng-click="checkPowerState()">check now</a> | 36 | <a href="" class="page-header__status-check" data-ng-show="canCheckPowerState()" data-ng-click="checkPowerState()">check now</a> |
639 | @@ -47,7 +47,7 @@ | |||
640 | 47 | <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> | 47 | <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
641 | 48 | <div class="page-header__dropdown" data-ng-class="{ 'is-open': actionOption }"> | 48 | <div class="page-header__dropdown" data-ng-class="{ 'is-open': actionOption }"> |
642 | 49 | 49 | ||
644 | 50 | <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="!node.dhcp_on"> | 50 | <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-if="!isDevice && !node.dhcp_on"> |
645 | 51 | <p class="page-header__message page-header__message--warning"> | 51 | <p class="page-header__message page-header__message--warning"> |
646 | 52 | MAAS is not providing DHCP. | 52 | MAAS is not providing DHCP. |
647 | 53 | </p> | 53 | </p> |
648 | @@ -89,58 +89,20 @@ | |||
649 | 89 | <div class="page-header__controls"> | 89 | <div class="page-header__controls"> |
650 | 90 | <a href="" class="button--base button--inline" data-ng-click="actionCancel()">Cancel</a> | 90 | <a href="" class="button--base button--inline" data-ng-click="actionCancel()">Cancel</a> |
651 | 91 | <button class="button--inline" data-ng-class="actionOption.name === 'delete' ? 'button--destructive' : 'button--positive'" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')"> | 91 | <button class="button--inline" data-ng-class="actionOption.name === 'delete' ? 'button--destructive' : 'button--positive'" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')"> |
704 | 92 | <span data-ng-if="actionOption.name === 'commission'">Commission | 92 | <span data-ng-if="actionOption.name === 'commission'">Commission {$ type_name $}</span> |
705 | 93 | <span data-ng-if="!isController">machine</span> | 93 | <span data-ng-if="actionOption.name === 'acquire'">Acquire {$ type_name $}</span> |
706 | 94 | <span data-ng-if="isController">controller</span> | 94 | <span data-ng-if="actionOption.name === 'deploy'">Deploy {$ type_name $}</span> |
707 | 95 | </span> | 95 | <span data-ng-if="actionOption.name === 'release'">Release {$ type_name $}</span> |
708 | 96 | <span data-ng-if="actionOption.name === 'acquire'">Acquire | 96 | <span data-ng-if="actionOption.name === 'set-zone'">Set zone for {$ type_name $}</span> |
709 | 97 | <span data-ng-if="!isController">machine</span> | 97 | <span data-ng-if="actionOption.name === 'on'">Power on {$ type_name $}</span> |
710 | 98 | <span data-ng-if="isController">controller</span> | 98 | <span data-ng-if="actionOption.name === 'off'">Power off {$ type_name $}</span> |
711 | 99 | </span> | 99 | <span data-ng-if="actionOption.name === 'abort'">Abort action on {$ type_name $}</span> |
712 | 100 | <span data-ng-if="actionOption.name === 'deploy'">Deploy | 100 | <span data-ng-if="actionOption.name === 'rescue-mode'">Rescue {$ type_name $}</span> |
713 | 101 | <span data-ng-if="!isController">machine</span> | 101 | <span data-ng-if="actionOption.name === 'exit-rescue-mode'">Exit rescue mode</span> |
714 | 102 | <span data-ng-if="isController">controller</span> | 102 | <span data-ng-if="actionOption.name === 'mark-broken'">Mark {$ type_name $}</span> |
715 | 103 | </span> | 103 | <span data-ng-if="actionOption.name === 'mark-fixed'">Mark {$ type_name $}</span> |
716 | 104 | <span data-ng-if="actionOption.name === 'release'">Release | 104 | <span data-ng-if="actionOption.name === 'delete'">Delete {$ type_name $}</span> |
717 | 105 | <span data-ng-if="!isController">machine</span> | 105 | <span data-ng-if="actionOption.name === 'import-images'">Import images</span> |
666 | 106 | <span data-ng-if="isController">controller</span> | ||
667 | 107 | </span> | ||
668 | 108 | <span data-ng-if="actionOption.name === 'set-zone'">Set zone for | ||
669 | 109 | <span data-ng-if="!isController">machine</span> | ||
670 | 110 | <span data-ng-if="isController">controller</span> | ||
671 | 111 | </span> | ||
672 | 112 | <span data-ng-if="actionOption.name === 'on'">Power on | ||
673 | 113 | <span data-ng-if="!isController">machine</span> | ||
674 | 114 | <span data-ng-if="isController">controller</span> | ||
675 | 115 | </span> | ||
676 | 116 | <span data-ng-if="actionOption.name === 'off'">Power off | ||
677 | 117 | <span data-ng-if="!isController">machine</span> | ||
678 | 118 | <span data-ng-if="isController">controller</span> | ||
679 | 119 | </span> | ||
680 | 120 | <span data-ng-if="actionOption.name === 'abort'">Abort action on | ||
681 | 121 | <span data-ng-if="!isController">machine</span> | ||
682 | 122 | <span data-ng-if="isController">controller</span> | ||
683 | 123 | </span> | ||
684 | 124 | <span data-ng-if="actionOption.name === 'rescue-mode'">Rescue | ||
685 | 125 | <span data-ng-if="!isController">machine</span> | ||
686 | 126 | <span data-ng-if="isController">controller</span> | ||
687 | 127 | </span> | ||
688 | 128 | <span data-ng-if="actionOption.name === 'exit-rescue-mode'">Exit rescue mode | ||
689 | 129 | </span> | ||
690 | 130 | <span data-ng-if="actionOption.name === 'mark-broken'">Mark | ||
691 | 131 | <span data-ng-if="!isController">machine</span> | ||
692 | 132 | <span data-ng-if="isController">controller</span> as broken | ||
693 | 133 | </span> | ||
694 | 134 | <span data-ng-if="actionOption.name === 'mark-fixed'">Mark | ||
695 | 135 | <span data-ng-if="!isController">machine</span> | ||
696 | 136 | <span data-ng-if="isController">controller</span> as fixed | ||
697 | 137 | </span> | ||
698 | 138 | <span data-ng-if="actionOption.name === 'delete'">Delete | ||
699 | 139 | <span data-ng-if="!isController">machine</span> | ||
700 | 140 | <span data-ng-if="isController">controller</span> | ||
701 | 141 | </span> | ||
702 | 142 | <span data-ng-if="actionOption.name === 'import-images'">Import images | ||
703 | 143 | </span> | ||
718 | 144 | </button> | 106 | </button> |
719 | 145 | </div> | 107 | </div> |
720 | 146 | </form> | 108 | </form> |
721 | @@ -201,12 +163,13 @@ | |||
722 | 201 | </nav> --> | 163 | </nav> --> |
723 | 202 | <div class="row" id="summary"> | 164 | <div class="row" id="summary"> |
724 | 203 | <div class="wrapper--inner"> | 165 | <div class="wrapper--inner"> |
728 | 204 | <h2 data-ng-hide="isController" class="eight-col">Machine summary</h2> | 166 | <h2 data-ng-if="!isController && !isDevice" class="eight-col">Machine summary</h2> |
729 | 205 | <h2 data-ng-show="isController" class="eight-col">Controller summary</h2> | 167 | <h2 data-ng-if="isController" class="eight-col">Controller summary</h2> |
730 | 206 | <div data-ng-hide="isController"> | 168 | <h2 data-ng-if="isDevice" class="eight-col">Device summary</h2> |
731 | 169 | <div data-ng-if="!isController"> | ||
732 | 207 | <div class="four-col last-col u-align--right" data-ng-hide="summary.editing"> | 170 | <div class="four-col last-col u-align--right" data-ng-hide="summary.editing"> |
733 | 208 | <a href="" class="button--secondary button--inline" | 171 | <a href="" class="button--secondary button--inline" |
735 | 209 | data-ng-show="canEdit()" | 172 | data-ng-if="canEdit()" |
736 | 210 | data-ng-click="editSummary()">Edit</a> | 173 | data-ng-click="editSummary()">Edit</a> |
737 | 211 | </div> | 174 | </div> |
738 | 212 | <div class="four-col last-col u-align--right ng-hide" data-ng-show="summary.editing"> | 175 | <div class="four-col last-col u-align--right ng-hide" data-ng-show="summary.editing"> |
739 | @@ -219,17 +182,17 @@ | |||
740 | 219 | </div> | 182 | </div> |
741 | 220 | <div class="twelve-col" data-ng-if="isSuperUser()"> | 183 | <div class="twelve-col" data-ng-if="isSuperUser()"> |
742 | 221 | <div class="p-notification--error" | 184 | <div class="p-notification--error" |
744 | 222 | data-ng-if="!isController && !isRackControllerConnected()"> | 185 | data-ng-if="!isController && !isDevice && !isRackControllerConnected()"> |
745 | 223 | <p class="p-notification__response"> | 186 | <p class="p-notification__response"> |
746 | 224 | <span class="p-notification__status">Error:</span>Editing is currently disabled because no rack controller is currently connected to the region.</p> | 187 | <span class="p-notification__status">Error:</span>Editing is currently disabled because no rack controller is currently connected to the region.</p> |
747 | 225 | </div> | 188 | </div> |
748 | 226 | <div class="p-notification--error" | 189 | <div class="p-notification--error" |
750 | 227 | data-ng-if="!isController && hasInvalidArchitecture() && isRackControllerConnected() && hasUsableArchitectures()"> | 190 | data-ng-if="!isController && !isDevice && hasInvalidArchitecture() && isRackControllerConnected() && hasUsableArchitectures()"> |
751 | 228 | <p class="p-notification__response"> | 191 | <p class="p-notification__response"> |
752 | 229 | <span class="p-notification__status">Error:</span>This machine currently has an invalid architecture. Update the architecture of this machine to make it deployable.</p> | 192 | <span class="p-notification__status">Error:</span>This machine currently has an invalid architecture. Update the architecture of this machine to make it deployable.</p> |
753 | 230 | </div> | 193 | </div> |
754 | 231 | <div class="p-notification--error" | 194 | <div class="p-notification--error" |
756 | 232 | data-ng-if="!isController && hasInvalidArchitecture() && isRackControllerConnected() && !hasUsableArchitectures()"> | 195 | data-ng-if="!isController && !isDevice && hasInvalidArchitecture() && isRackControllerConnected() && !hasUsableArchitectures()"> |
757 | 233 | <p class="p-notification__response"> | 196 | <p class="p-notification__response"> |
758 | 234 | <span class="p-notification__status">Error:</span>No boot images have been imported for a valid architecture to be selected. Visit the <a href="images">images page</a> to start the import process.</p> | 197 | <span class="p-notification__status">Error:</span>No boot images have been imported for a valid architecture to be selected. Visit the <a href="images">images page</a> to start the import process.</p> |
759 | 235 | </div> | 198 | </div> |
760 | @@ -247,7 +210,7 @@ | |||
761 | 247 | </select> | 210 | </select> |
762 | 248 | </div> | 211 | </div> |
763 | 249 | </div> | 212 | </div> |
765 | 250 | <div class="form__group"> | 213 | <div class="form__group" data-ng-if="!isDevice"> |
766 | 251 | <label for="architecture" class="form__group-label two-col">Architecture</label> | 214 | <label for="architecture" class="form__group-label two-col">Architecture</label> |
767 | 252 | <div class="form__group-input three-col"> | 215 | <div class="form__group-input three-col"> |
768 | 253 | <select name="architecture" id="architecture" | 216 | <select name="architecture" id="architecture" |
769 | @@ -259,7 +222,7 @@ | |||
770 | 259 | </select> | 222 | </select> |
771 | 260 | </div> | 223 | </div> |
772 | 261 | </div> | 224 | </div> |
774 | 262 | <div class="form__group"> | 225 | <div class="form__group" data-ng-if="!isDevice"> |
775 | 263 | <label for="min_hwe_kernel" class="form__group-label two-col">Minimum Kernel</label> | 226 | <label for="min_hwe_kernel" class="form__group-label two-col">Minimum Kernel</label> |
776 | 264 | <div class="form__group-input three-col"> | 227 | <div class="form__group-input three-col"> |
777 | 265 | <select name="min_hwe_kernel" id="min_hwe_kernel" placeholder="No minimum kernel" | 228 | <select name="min_hwe_kernel" id="min_hwe_kernel" placeholder="No minimum kernel" |
778 | @@ -293,7 +256,7 @@ | |||
779 | 293 | <dd class="four-col last-col"> | 256 | <dd class="four-col last-col"> |
780 | 294 | {$ node.zone.name $} | 257 | {$ node.zone.name $} |
781 | 295 | </dd> | 258 | </dd> |
783 | 296 | <dt class="two-col">Architecture</dt> | 259 | <dt class="two-col" data-ng-if="!isDevice">Architecture</dt> |
784 | 297 | <dd class="four-col last-col"> | 260 | <dd class="four-col last-col"> |
785 | 298 | {$ node.architecture || "Missing" $} | 261 | {$ node.architecture || "Missing" $} |
786 | 299 | </dd> | 262 | </dd> |
787 | @@ -336,7 +299,7 @@ | |||
788 | 336 | </dd> | 299 | </dd> |
789 | 337 | </dl> | 300 | </dl> |
790 | 338 | </div> | 301 | </div> |
792 | 339 | <div class="six-col last-col"> | 302 | <div class="six-col last-col" data-ng-if="!isDevice"> |
793 | 340 | <dl> | 303 | <dl> |
794 | 341 | <dt class="two-col">CPU</dt> | 304 | <dt class="two-col">CPU</dt> |
795 | 342 | <dd class="four-col last-col"> | 305 | <dd class="four-col last-col"> |
796 | @@ -388,7 +351,7 @@ | |||
797 | 388 | </div> | 351 | </div> |
798 | 389 | </div> | 352 | </div> |
799 | 390 | </div> | 353 | </div> |
801 | 391 | <div class="row" id="power" data-ng-show="!isController && isSuperUser()"> | 354 | <div class="row" id="power" data-ng-if="!isController && !isDevice && isSuperUser()"> |
802 | 392 | <div class="wrapper--inner"> | 355 | <div class="wrapper--inner"> |
803 | 393 | <h2 class="eight-col">Power</h2> | 356 | <h2 class="eight-col">Power</h2> |
804 | 394 | <div class="four-col last-col u-align--right" data-ng-hide="power.editing"> | 357 | <div class="four-col last-col u-align--right" data-ng-hide="power.editing"> |
805 | @@ -613,8 +576,8 @@ | |||
806 | 613 | <h2 class="title">Interfaces</h2> | 576 | <h2 class="title">Interfaces</h2> |
807 | 614 | </div> | 577 | </div> |
808 | 615 | <div class="twelve-col" data-ng-if=" | 578 | <div class="twelve-col" data-ng-if=" |
811 | 616 | (!isController && isAllNetworkingDisabled() && isSuperUser()) || | 579 | !isDevice && ((!isController && isAllNetworkingDisabled() && isSuperUser()) || |
812 | 617 | !node.on_network || (!isController && !isUbuntuOS())"> | 580 | !node.on_network || (!isController && !isUbuntuOS()))"> |
813 | 618 | <div class="p-notification--error" data-ng-if="!node.on_network"> | 581 | <div class="p-notification--error" data-ng-if="!node.on_network"> |
814 | 619 | <p class="p-notification__response"> | 582 | <p class="p-notification__response"> |
815 | 620 | <span class="p-notification__status">Error:</span>Node must be connected to a network.</p> | 583 | <span class="p-notification__status">Error:</span>Node must be connected to a network.</p> |
816 | @@ -635,6 +598,7 @@ | |||
817 | 635 | <div class="table__row"> | 598 | <div class="table__row"> |
818 | 636 | <div class="table__header table-col--3"></div> | 599 | <div class="table__header table-col--3"></div> |
819 | 637 | <div class="table__header table-col--12"> | 600 | <div class="table__header table-col--12"> |
820 | 601 | <span data-ng-if="!isDevice"> | ||
821 | 638 | <a class="table__header-link is-active" | 602 | <a class="table__header-link is-active" |
822 | 639 | data-ng-class="{ 'is-active': column == 'name' }" | 603 | data-ng-class="{ 'is-active': column == 'name' }" |
823 | 640 | data-ng-click="column = 'name'"> | 604 | data-ng-click="column = 'name'"> |
824 | @@ -645,11 +609,16 @@ | |||
825 | 645 | data-ng-click="column = 'mac'"> | 609 | data-ng-click="column = 'mac'"> |
826 | 646 | MAC | 610 | MAC |
827 | 647 | </a> | 611 | </a> |
833 | 648 | </div> | 612 | </span> |
834 | 649 | <div class="table__header table-col--3"><span data-ng-if="!isController">PXE</span></div> | 613 | <span data-ng-if="isDevice">MAC</span> |
835 | 650 | <div class="table__header table-col--9">Type</div> | 614 | </div> |
836 | 651 | <div class="table__header table-col--14">Fabric</div> | 615 | <div class="table__header table-col--3"><span data-ng-if="!isController && !isDevice">PXE</span></div> |
837 | 652 | <div class="table__header table-col--14">VLAN</div> | 616 | <div class="table__header table-col--9"><span data-ng-if="!isDevice">Type</span></div> |
838 | 617 | <div class="table__header table-col--14"> | ||
839 | 618 | <span data-ng-if="!isDevice">Fabric</span> | ||
840 | 619 | <span data-ng-if="isDevice">IP Assignment</span> | ||
841 | 620 | </div> | ||
842 | 621 | <div class="table__header table-col--14"><span data-ng-if="!isDevice">VLAN</span></div> | ||
843 | 653 | <div class="table__header table-col--18">Subnet</div> | 622 | <div class="table__header table-col--18">Subnet</div> |
844 | 654 | <div class="table__header table-col--27">IP Address</div> | 623 | <div class="table__header table-col--27">IP Address</div> |
845 | 655 | </div> | 624 | </div> |
846 | @@ -667,43 +636,48 @@ | |||
847 | 667 | data-ng-if="!isController && isNodeEditingAllowed()"> | 636 | data-ng-if="!isController && isNodeEditingAllowed()"> |
848 | 668 | <label for="{$ getUniqueKey(interface) $}"></label> | 637 | <label for="{$ getUniqueKey(interface) $}"></label> |
849 | 669 | </div> | 638 | </div> |
851 | 670 | <div class="table__data table-col--12" aria-label="Name" data-ng-show="column == 'name'"> | 639 | <div class="table__data table-col--12" aria-label="Name" data-ng-if="!isDevice" data-ng-show="column == 'name'"> |
852 | 671 | <span data-ng-if="!isEditing(interface)">{$ interface.name $}</span> | 640 | <span data-ng-if="!isEditing(interface)">{$ interface.name $}</span> |
853 | 672 | <input type="text" class="table__input" | 641 | <input type="text" class="table__input" |
854 | 673 | data-ng-if="isEditing(interface) && interface.type != 'vlan'" | 642 | data-ng-if="isEditing(interface) && interface.type != 'vlan'" |
855 | 674 | data-ng-model="editInterface.name" | 643 | data-ng-model="editInterface.name" |
856 | 675 | data-ng-class="{ 'has-error': isInterfaceNameInvalid(editInterface) }"> | 644 | data-ng-class="{ 'has-error': isInterfaceNameInvalid(editInterface) }"> |
857 | 676 | </div> | 645 | </div> |
859 | 677 | <div class="table__data table-col--12 ng-hide" data-ng-show="column == 'mac'"> | 646 | <div class="table__data table-col--12 ng-hide" data-ng-if="!isDevice" data-ng-show="column == 'mac'"> |
860 | 678 | {$ interface.mac_address $} | 647 | {$ interface.mac_address $} |
861 | 679 | </div> | 648 | </div> |
862 | 649 | <div class="table__data table-col--12" data-ng-if="isDevice">{$ interface.mac_address $}</div> | ||
863 | 680 | <div class="table__data table-col--3 u-align--center" aria-label="Boot interface"> | 650 | <div class="table__data table-col--3 u-align--center" aria-label="Boot interface"> |
864 | 681 | <input type="radio" name="bootInterface" id="{$ interface.name $}" checked | 651 | <input type="radio" name="bootInterface" id="{$ interface.name $}" checked |
866 | 682 | data-ng-if="!isController && isBootInterface(interface)" class="u-display--desktop"> | 652 | data-ng-if="!isController && !isDevice && isBootInterface(interface)" class="u-display--desktop"> |
867 | 683 | <label for="{$ interface.name $}" class="u-display--desktop"></label> | 653 | <label for="{$ interface.name $}" class="u-display--desktop"></label> |
870 | 684 | <span class="u-display--mobile" data-ng-if="!isController && isBootInterface(interface)">Yes</span> | 654 | <span class="u-display--mobile" data-ng-if="!isController && !isDevice && isBootInterface(interface)">Yes</span> |
871 | 685 | <span class="u-display--mobile" data-ng-if="!isController && !isBootInterface(interface)">No</span> | 655 | <span class="u-display--mobile" data-ng-if="!isController && !isDevice && !isBootInterface(interface)">No</span> |
872 | 686 | </div> | 656 | </div> |
873 | 687 | <div class="table__data table-col--9" aria-label="Type"> | 657 | <div class="table__data table-col--9" aria-label="Type"> |
875 | 688 | <span data-ng-if="!isEditing(interface)">{$ getInterfaceTypeText(interface) $}</span> | 658 | <span data-ng-if="!isDevice && !isEditing(interface)">{$ getInterfaceTypeText(interface) $}</span> |
876 | 689 | </div> | 659 | </div> |
877 | 690 | <div class="table__data table-col--14" aria-label="Fabric"> | 660 | <div class="table__data table-col--14" aria-label="Fabric"> |
879 | 691 | <span data-ng-if="!isEditing(interface)">{$ interface.fabric.name || "Disconnected" $}</span> | 661 | <span data-ng-if="!isDevice && !isEditing(interface)">{$ interface.fabric.name || "Disconnected" $}</span> |
880 | 662 | <span data-ng-if="isDevice && !isEditing(interface)">{$ getDeviceIPAssignment(interface.ip_assignment) $}</span> | ||
881 | 692 | </div> | 663 | </div> |
882 | 693 | <div class="table__data table-col--14" aria-label="VLAN"> | 664 | <div class="table__data table-col--14" aria-label="VLAN"> |
884 | 694 | <span data-ng-if="!isEditing(interface)">{$ getVLANText(interface.vlan) $}</span> | 665 | <span data-ng-if="!isDevice && !isEditing(interface)">{$ getVLANText(interface.vlan) $}</span> |
885 | 695 | </div> | 666 | </div> |
886 | 696 | <div class="table__data table-col--18" aria-label="Subnet"> | 667 | <div class="table__data table-col--18" aria-label="Subnet"> |
888 | 697 | <span data-ng-if="!isEditing(interface) && interface.fabric">{$ getSubnetText(interface.subnet) $}</span> | 668 | <span data-ng-if="!isEditing(interface) && ((isDevice && interface.subnet) || interface.fabric)">{$ getSubnetText(interface.subnet) $}</span> |
889 | 698 | <span data-ng-if="isAllNetworkingDisabled() && interface.discovered[0].subnet_id"> | 669 | <span data-ng-if="isAllNetworkingDisabled() && interface.discovered[0].subnet_id"> |
890 | 699 | {$ getSubnetText(getSubnet(interface.discovered[0].subnet_id)) $} | 670 | {$ getSubnetText(getSubnet(interface.discovered[0].subnet_id)) $} |
891 | 700 | </span> | 671 | </span> |
892 | 701 | </div> | 672 | </div> |
893 | 702 | <div class="table__data table-col--19" aria-label="IP Address"> | 673 | <div class="table__data table-col--19" aria-label="IP Address"> |
895 | 703 | <span data-ng-if="!isEditing(interface) && !interface.discovered[0].ip_address && interface.fabric"> | 674 | <span data-ng-if="isDevice"> |
896 | 675 | {$ interface.ip_address $} | ||
897 | 676 | </span> | ||
898 | 677 | <span data-ng-if="!isDevice && !isEditing(interface) && !interface.discovered[0].ip_address && interface.fabric" > | ||
899 | 704 | {$ interface.ip_address $} ({$ getLinkModeText(interface) $}) | 678 | {$ interface.ip_address $} ({$ getLinkModeText(interface) $}) |
900 | 705 | </span> | 679 | </span> |
902 | 706 | <span data-ng-if="!isEditing(interface) && interface.discovered[0].ip_address && interface.fabric"> | 680 | <span data-ng-if="!isDevice && !isEditing(interface) && interface.discovered[0].ip_address && interface.fabric"> |
903 | 707 | {$ interface.discovered[0].ip_address $} (DHCP) | 681 | {$ interface.discovered[0].ip_address $} (DHCP) |
904 | 708 | </span> | 682 | </span> |
905 | 709 | </div> | 683 | </div> |
906 | @@ -711,7 +685,7 @@ | |||
907 | 711 | <div class="table__controls u-align--right" data-ng-if="isNodeEditingAllowed() || isLimitedEditingAllowed(interface)"> | 685 | <div class="table__controls u-align--right" data-ng-if="isNodeEditingAllowed() || isLimitedEditingAllowed(interface)"> |
908 | 712 | <a class="u-display--desktop icon icon--add tooltip" | 686 | <a class="u-display--desktop icon icon--add tooltip" |
909 | 713 | aria-label="Add Alias or VLAN" | 687 | aria-label="Add Alias or VLAN" |
911 | 714 | data-ng-if="canAddAliasOrVLAN(interface)" | 688 | data-ng-if="!isDevice && canAddAliasOrVLAN(interface)" |
912 | 715 | data-ng-click="quickAdd(interface)"></a> | 689 | data-ng-click="quickAdd(interface)"></a> |
913 | 716 | <a class="u-display--desktop icon icon--edit tooltip" | 690 | <a class="u-display--desktop icon icon--edit tooltip" |
914 | 717 | data-ng-if="!cannotEditInterface(interface)" | 691 | data-ng-if="!cannotEditInterface(interface)" |
915 | @@ -723,7 +697,7 @@ | |||
916 | 723 | data-ng-click="quickRemove(interface)"></a> | 697 | data-ng-click="quickRemove(interface)"></a> |
917 | 724 | <a class="button--secondary u-display--mobile" | 698 | <a class="button--secondary u-display--mobile" |
918 | 725 | aria-label="Add Alias or VLAN" | 699 | aria-label="Add Alias or VLAN" |
920 | 726 | data-ng-if="canAddAliasOrVLAN(interface)" | 700 | data-ng-if="!isDevice && canAddAliasOrVLAN(interface)" |
921 | 727 | data-ng-click="quickAdd(interface)">Add alias or VLAN</a> | 701 | data-ng-click="quickAdd(interface)">Add alias or VLAN</a> |
922 | 728 | <a class="button--secondary u-display--mobile" | 702 | <a class="button--secondary u-display--mobile" |
923 | 729 | data-ng-if="!cannotEditInterface(interface)" | 703 | data-ng-if="!cannotEditInterface(interface)" |
924 | @@ -837,8 +811,10 @@ | |||
925 | 837 | <div class="table__data table-col--100" data-ng-if="isEditing(interface)"> | 811 | <div class="table__data table-col--100" data-ng-if="isEditing(interface)"> |
926 | 838 | <fieldset class="form__fieldset six-col"> | 812 | <fieldset class="form__fieldset six-col"> |
927 | 839 | <dl> | 813 | <dl> |
928 | 814 | <span data-ng-if="!isDevice"> | ||
929 | 840 | <dt class="two-col">Type</dt> | 815 | <dt class="two-col">Type</dt> |
930 | 841 | <dd class="four-col last-col">{$ getInterfaceTypeText(interface) $}</dd> | 816 | <dd class="four-col last-col">{$ getInterfaceTypeText(interface) $}</dd> |
931 | 817 | </span> | ||
932 | 842 | </dl> | 818 | </dl> |
933 | 843 | <div class="form__group" data-ng-if="interface.type != 'alias' && interface.type != 'vlan'"> | 819 | <div class="form__group" data-ng-if="interface.type != 'alias' && interface.type != 'vlan'"> |
934 | 844 | <label for="mac" class="two-col">MAC address</label> | 820 | <label for="mac" class="two-col">MAC address</label> |
935 | @@ -847,14 +823,14 @@ | |||
936 | 847 | data-ng-model="editInterface.mac_address" | 823 | data-ng-model="editInterface.mac_address" |
937 | 848 | data-ng-class="{ 'has-error': isMACAddressInvalid(editInterface.mac_address, true) }"> | 824 | data-ng-class="{ 'has-error': isMACAddressInvalid(editInterface.mac_address, true) }"> |
938 | 849 | </div> | 825 | </div> |
940 | 850 | <div class="form__group" data-ng-if="!isLimitedEditingAllowed(interface)"> | 826 | <div class="form__group" data-ng-if="!isDevice && !isLimitedEditingAllowed(interface)"> |
941 | 851 | <label class="two-col" data-ng-if="interface.type != 'alias'">Tags</label> | 827 | <label class="two-col" data-ng-if="interface.type != 'alias'">Tags</label> |
942 | 852 | <div class="form__group-input three-col last-col" data-ng-if="interface.type != 'alias'"> | 828 | <div class="form__group-input three-col last-col" data-ng-if="interface.type != 'alias'"> |
943 | 853 | <tags-input ng-model="editInterface.tags" allow-tags-pattern="[\w-]+"></tags-input> | 829 | <tags-input ng-model="editInterface.tags" allow-tags-pattern="[\w-]+"></tags-input> |
944 | 854 | </div> | 830 | </div> |
945 | 855 | </div> | 831 | </div> |
946 | 856 | </fieldset> | 832 | </fieldset> |
948 | 857 | <fieldset class="form__fieldset six-col last-col" data-ng-if="!isLimitedEditingAllowed(interface)"> | 833 | <fieldset class="form__fieldset six-col last-col" data-ng-if="!isDevice && !isLimitedEditingAllowed(interface)"> |
949 | 858 | <div class="form__group"> | 834 | <div class="form__group"> |
950 | 859 | <label for="fabric" class="two-col">Fabric</label> | 835 | <label for="fabric" class="two-col">Fabric</label> |
951 | 860 | <select name="fabric" class="three-col" | 836 | <select name="fabric" class="three-col" |
952 | @@ -904,6 +880,37 @@ | |||
953 | 904 | </div> | 880 | </div> |
954 | 905 | </div> | 881 | </div> |
955 | 906 | </fieldset> | 882 | </fieldset> |
956 | 883 | <fieldset class="form__fieldset six-col last-col" data-ng-if="isDevice && !isLimitedEditingAllowed(interface)"> | ||
957 | 884 | <div class="form__group"> | ||
958 | 885 | <label for="ip-assignment" class="two-col">IP Assignment</label> | ||
959 | 886 | <select name="ip-assignment" class="three-col" | ||
960 | 887 | ng-model="editInterface.ip_assignment" | ||
961 | 888 | data-ng-change="ipAssignementChanged(editInterface)" | ||
962 | 889 | data-ng-options="assignment.name as assignment.text for assignment in ipAssignments"> | ||
963 | 890 | </select> | ||
964 | 891 | </div> | ||
965 | 892 | <div class="form__group" data-ng-if="editInterface.ip_assignment === 'static'"> | ||
966 | 893 | <label for="subnet" class="two-col">Subnet</label> | ||
967 | 894 | <select name="subnet" class="three-col" | ||
968 | 895 | ng-model="editInterface.subnet" | ||
969 | 896 | data-ng-change="subnetChanged(newInterface)" | ||
970 | 897 | data-ng-options="subnet as getSubnetText(subnet) for subnet in subnets"> | ||
971 | 898 | <option value="" data-ng-hide="interface.links.length > 1">Unconfigured</option> | ||
972 | 899 | </select> | ||
973 | 900 | </div> | ||
974 | 901 | <div class="form__group" data-ng-if="editInterface.ip_assignment !== 'dynamic'"> | ||
975 | 902 | <label for="ip-address" class="two-col">IP address</label> | ||
976 | 903 | <div class="form__group-input three-col last-col"> | ||
977 | 904 | <input name="ip-address" type="text" | ||
978 | 905 | placeholder="IP address (optional)" | ||
979 | 906 | ng-model="editInterface.ip_address" | ||
980 | 907 | data-ng-class="{ 'has-error': isIPAddressInvalid(editInterface) }"> | ||
981 | 908 | <ul class="errors u-margin--bottom-none form__group--errors" data-ng-if="getInterfaceError(editInterface)"> | ||
982 | 909 | <li>{$ getInterfaceError(editInterface) $}</li> | ||
983 | 910 | </ul> | ||
984 | 911 | </div> | ||
985 | 912 | </div> | ||
986 | 913 | </fieldset> | ||
987 | 907 | </div> | 914 | </div> |
988 | 908 | </div> | 915 | </div> |
989 | 909 | <div class="table__row is-active" data-ng-if="isShowingDeleteConfirm()"> | 916 | <div class="table__row is-active" data-ng-if="isShowingDeleteConfirm()"> |
990 | @@ -1124,7 +1131,7 @@ | |||
991 | 1124 | <input type="checkbox" class="checkbox" id="interface-create" disabled="disabled" checked /> | 1131 | <input type="checkbox" class="checkbox" id="interface-create" disabled="disabled" checked /> |
992 | 1125 | <label for="interface-create"></label> | 1132 | <label for="interface-create"></label> |
993 | 1126 | </div> | 1133 | </div> |
995 | 1127 | <div class="table__data table-col--12"> | 1134 | <div class="table__data table-col--12" data-ng-if="!isDevice"> |
996 | 1128 | <input type="text" class="table__input" | 1135 | <input type="text" class="table__input" |
997 | 1129 | data-ng-class="{ 'has-error': isInterfaceNameInvalid(newInterface) }" | 1136 | data-ng-class="{ 'has-error': isInterfaceNameInvalid(newInterface) }" |
998 | 1130 | data-ng-model="newInterface.name"> | 1137 | data-ng-model="newInterface.name"> |
999 | @@ -1138,7 +1145,15 @@ | |||
1000 | 1138 | </div> | 1145 | </div> |
1001 | 1139 | <div class="table__row form form--stack is-active"> | 1146 | <div class="table__row form form--stack is-active"> |
1002 | 1140 | <div class="table__data table-col--100"> | 1147 | <div class="table__data table-col--100"> |
1004 | 1141 | <div class="form__fieldset six-col"> | 1148 | <div class="table__data table-col--12" data-ng-if="isDevice"> |
1005 | 1149 | <div class="form__group"> | ||
1006 | 1150 | <label for="mac" class="three-col">MAC address</label> | ||
1007 | 1151 | <input type="text" name="mac" class="table__input" | ||
1008 | 1152 | data-ng-model="newInterface.macAddress" | ||
1009 | 1153 | data-ng-class="{ 'has-error': isMACAddressInvalid(newInterface.macAddress, false) || newInterface.macError }"> | ||
1010 | 1154 | </div> | ||
1011 | 1155 | </div> | ||
1012 | 1156 | <div class="form__fieldset six-col" data-ng-if="!isDevice"> | ||
1013 | 1142 | <div class="form__group u-display--mobile"> | 1157 | <div class="form__group u-display--mobile"> |
1014 | 1143 | <label for="new-interface-name" class="two-col">Name</label> | 1158 | <label for="new-interface-name" class="two-col">Name</label> |
1015 | 1144 | <input type="text" class="three-col" id="new-interface-name" | 1159 | <input type="text" class="three-col" id="new-interface-name" |
1016 | @@ -1162,7 +1177,8 @@ | |||
1017 | 1162 | </div> | 1177 | </div> |
1018 | 1163 | </div> | 1178 | </div> |
1019 | 1164 | </div> | 1179 | </div> |
1021 | 1165 | <div class="form__fieldset six-col last-col"> | 1180 | <span data-ng-if="!isDevice"> |
1022 | 1181 | <div class="form__fieldset six-col last-col"> | ||
1023 | 1166 | <div class="form__group"> | 1182 | <div class="form__group"> |
1024 | 1167 | <label for="fabric" class="two-col">Fabric</label> | 1183 | <label for="fabric" class="two-col">Fabric</label> |
1025 | 1168 | <select name="fabric" class="three-col" | 1184 | <select name="fabric" class="three-col" |
1026 | @@ -1203,7 +1219,41 @@ | |||
1027 | 1203 | ng-model="newInterface.ip_address" | 1219 | ng-model="newInterface.ip_address" |
1028 | 1204 | data-ng-class="{ 'has-error': isIPAddressInvalid(newInterface) }"> | 1220 | data-ng-class="{ 'has-error': isIPAddressInvalid(newInterface) }"> |
1029 | 1205 | </div> | 1221 | </div> |
1031 | 1206 | </div> | 1222 | </div> |
1032 | 1223 | </span> | ||
1033 | 1224 | <span data-ng-if="isDevice"> | ||
1034 | 1225 | <div class="form__fieldset six-col last-col"> | ||
1035 | 1226 | <div class="form__group"> | ||
1036 | 1227 | <label for="ip-assignment" class="two-col">IP Assignment</label> | ||
1037 | 1228 | <select name="ip-assignment" class="three-col" | ||
1038 | 1229 | ng-model="newInterface.ip_assignment" | ||
1039 | 1230 | data-ng-change="ipAssignementChanged(newInterface)" | ||
1040 | 1231 | data-ng-options="assignment.name as assignment.text for assignment in ipAssignments"> | ||
1041 | 1232 | </select> | ||
1042 | 1233 | </div> | ||
1043 | 1234 | <div class="form__group" data-ng-if="newInterface.ip_assignment === 'static'"> | ||
1044 | 1235 | <label for="subnet" class="two-col">Subnet</label> | ||
1045 | 1236 | <select name="subnet" class="three-col" | ||
1046 | 1237 | ng-model="newInterface.subnet" | ||
1047 | 1238 | data-ng-change="subnetChanged(newInterface)" | ||
1048 | 1239 | data-ng-options="subnet as getSubnetText(subnet) for subnet in subnets"> | ||
1049 | 1240 | <option value="" data-ng-hide="interface.links.length > 1">Unconfigured</option> | ||
1050 | 1241 | </select> | ||
1051 | 1242 | </div> | ||
1052 | 1243 | <div class="form__group" data-ng-if="newInterface.ip_assignment !== 'dynamic'"> | ||
1053 | 1244 | <label for="ip-address" class="two-col">IP address</label> | ||
1054 | 1245 | <div class="form__group-input three-col last-col"> | ||
1055 | 1246 | <input name="ip-address" type="text" | ||
1056 | 1247 | placeholder="IP address (optional)" | ||
1057 | 1248 | ng-model="newInterface.ip_address" | ||
1058 | 1249 | data-ng-class="{ 'has-error': isIPAddressInvalid(newInterface) }"> | ||
1059 | 1250 | <ul class="errors u-margin--bottom-none form__group--errors" data-ng-if="getInterfaceError(newInterface)"> | ||
1060 | 1251 | <li>{$ getInterfaceError(newInterface) $}</li> | ||
1061 | 1252 | </ul> | ||
1062 | 1253 | </div> | ||
1063 | 1254 | </div> | ||
1064 | 1255 | </div> | ||
1065 | 1256 | </span> | ||
1066 | 1207 | </div> | 1257 | </div> |
1067 | 1208 | </div> | 1258 | </div> |
1068 | 1209 | <div class="table__row is-active"> | 1259 | <div class="table__row is-active"> |
1069 | @@ -1221,7 +1271,7 @@ | |||
1070 | 1221 | </div> | 1271 | </div> |
1071 | 1222 | </main> | 1272 | </main> |
1072 | 1223 | </div> | 1273 | </div> |
1074 | 1224 | <div data-ng-if="!isController" data-ng-hide="isAllNetworkingDisabled() || isShowingCreateBond() || isShowingCreatePhysical()"> | 1274 | <div data-ng-if="!isController && !isDevice" data-ng-hide="isAllNetworkingDisabled() || isShowingCreateBond() || isShowingCreatePhysical()"> |
1075 | 1225 | <a class="button--secondary button--inline" | 1275 | <a class="button--secondary button--inline" |
1076 | 1226 | data-ng-disabled="selectedMode !== null" | 1276 | data-ng-disabled="selectedMode !== null" |
1077 | 1227 | data-ng-click="showCreatePhysical()">Add interface</a> | 1277 | data-ng-click="showCreatePhysical()">Add interface</a> |
1078 | @@ -1232,12 +1282,17 @@ | |||
1079 | 1232 | data-ng-disabled="!canCreateBridge()" | 1282 | data-ng-disabled="!canCreateBridge()" |
1080 | 1233 | data-ng-click="showCreateBridge()">Create bridge</a> | 1283 | data-ng-click="showCreateBridge()">Create bridge</a> |
1081 | 1234 | </div> | 1284 | </div> |
1082 | 1285 | <div data-ng-if="isDevice" data-ng-hide="isAllNetworkingDisabled() || isShowingCreateBond() || isShowingCreatePhysical()"> | ||
1083 | 1286 | <a class="button--secondary button--inline" | ||
1084 | 1287 | data-ng-disabled="selectedMode !== null" | ||
1085 | 1288 | data-ng-click="showCreatePhysical()">Add interface</a> | ||
1086 | 1289 | </div> | ||
1087 | 1235 | </div> | 1290 | </div> |
1088 | 1236 | </form> | 1291 | </form> |
1089 | 1237 | </div> | 1292 | </div> |
1090 | 1238 | </div> | 1293 | </div> |
1091 | 1239 | </div> | 1294 | </div> |
1093 | 1240 | <div class="row" id="storage" data-ng-hide="isController"> | 1295 | <div class="row" id="storage" data-ng-if="!isController && !isDevice"> |
1094 | 1241 | <div class="wrapper--inner" ng-controller="NodeStorageController"> | 1296 | <div class="wrapper--inner" ng-controller="NodeStorageController"> |
1095 | 1242 | <form> | 1297 | <form> |
1096 | 1243 | <div class="twelve-col"> | 1298 | <div class="twelve-col"> |
1097 | @@ -2223,7 +2278,7 @@ | |||
1098 | 2223 | </form> | 2278 | </form> |
1099 | 2224 | </div> | 2279 | </div> |
1100 | 2225 | </div> | 2280 | </div> |
1102 | 2226 | <div class="row" id="events" data-ng-hide="isController"> | 2281 | <div class="row" id="events" data-ng-if="!isController && !isDevice"> |
1103 | 2227 | <div class="wrapper--inner"> | 2282 | <div class="wrapper--inner"> |
1104 | 2228 | <div class="eight-col"> | 2283 | <div class="eight-col"> |
1105 | 2229 | <h2 class="title">Latest {$ type_name $} events</h2> | 2284 | <h2 class="title">Latest {$ type_name $} events</h2> |
1106 | 2230 | 2285 | ||
1107 | === modified file 'src/maasserver/static/partials/nodes-list.html' | |||
1108 | --- src/maasserver/static/partials/nodes-list.html 2017-02-17 14:23:04 +0000 | |||
1109 | +++ src/maasserver/static/partials/nodes-list.html 2017-02-28 04:16:58 +0000 | |||
1110 | @@ -902,7 +902,9 @@ | |||
1111 | 902 | <input type="checkbox" class="checkbox" data-ng-click="toggleChecked(device, 'devices')" data-ng-checked="device.$selected" id="{$ device.fqdn $}" data-ng-disabled="hasActionsInProgress('devices')"/> | 902 | <input type="checkbox" class="checkbox" data-ng-click="toggleChecked(device, 'devices')" data-ng-checked="device.$selected" id="{$ device.fqdn $}" data-ng-disabled="hasActionsInProgress('devices')"/> |
1112 | 903 | <label for="{$ device.fqdn $}" class="checkbox-label"></label> | 903 | <label for="{$ device.fqdn $}" class="checkbox-label"></label> |
1113 | 904 | </td> | 904 | </td> |
1115 | 905 | <td class="table-col--24" aria-label="FQDN">{$ device.fqdn $}</td> | 905 | <td class="table-col--24" aria-label="FQDN"> |
1116 | 906 | <a href="#/node/device/{$ device.system_id $}">{$ device.fqdn $}</a> | ||
1117 | 907 | </td> | ||
1118 | 906 | <td class="table-col--25" aria-label="MAC"> | 908 | <td class="table-col--25" aria-label="MAC"> |
1119 | 907 | {$ device.primary_mac $} | 909 | {$ device.primary_mac $} |
1120 | 908 | <span class="extra-macs" data-ng-show="device.extra_macs.length">(+{$ device.extra_macs.length $})</span> | 910 | <span class="extra-macs" data-ng-show="device.extra_macs.length">(+{$ device.extra_macs.length $})</span> |
1121 | 909 | 911 | ||
1122 | === modified file 'src/maasserver/websockets/handlers/device.py' | |||
1123 | --- src/maasserver/websockets/handlers/device.py 2017-02-17 21:27:46 +0000 | |||
1124 | +++ src/maasserver/websockets/handlers/device.py 2017-02-28 04:16:58 +0000 | |||
1125 | @@ -7,7 +7,9 @@ | |||
1126 | 7 | "DeviceHandler", | 7 | "DeviceHandler", |
1127 | 8 | ] | 8 | ] |
1128 | 9 | 9 | ||
1129 | 10 | from django.core.exceptions import ValidationError | ||
1130 | 10 | from maasserver.enum import ( | 11 | from maasserver.enum import ( |
1131 | 12 | DEVICE_IP_ASSIGNMENT_TYPE, | ||
1132 | 11 | INTERFACE_LINK_TYPE, | 13 | INTERFACE_LINK_TYPE, |
1133 | 12 | IPADDRESS_TYPE, | 14 | IPADDRESS_TYPE, |
1134 | 13 | NODE_PERMISSION, | 15 | NODE_PERMISSION, |
1135 | @@ -17,7 +19,11 @@ | |||
1136 | 17 | DeviceForm, | 19 | DeviceForm, |
1137 | 18 | DeviceWithMACsForm, | 20 | DeviceWithMACsForm, |
1138 | 19 | ) | 21 | ) |
1140 | 20 | from maasserver.forms.interface import PhysicalInterfaceForm | 22 | from maasserver.forms.interface import ( |
1141 | 23 | InterfaceForm, | ||
1142 | 24 | PhysicalInterfaceForm, | ||
1143 | 25 | ) | ||
1144 | 26 | from maasserver.models.interface import Interface | ||
1145 | 21 | from maasserver.models.node import Device | 27 | from maasserver.models.node import Device |
1146 | 22 | from maasserver.models.staticipaddress import StaticIPAddress | 28 | from maasserver.models.staticipaddress import StaticIPAddress |
1147 | 23 | from maasserver.models.subnet import Subnet | 29 | from maasserver.models.subnet import Subnet |
1148 | @@ -26,6 +32,7 @@ | |||
1149 | 26 | from maasserver.websockets.base import ( | 32 | from maasserver.websockets.base import ( |
1150 | 27 | HandlerDoesNotExistError, | 33 | HandlerDoesNotExistError, |
1151 | 28 | HandlerError, | 34 | HandlerError, |
1152 | 35 | HandlerPermissionError, | ||
1153 | 29 | HandlerValidationError, | 36 | HandlerValidationError, |
1154 | 30 | ) | 37 | ) |
1155 | 31 | from maasserver.websockets.handlers.node import ( | 38 | from maasserver.websockets.handlers.node import ( |
1156 | @@ -39,20 +46,6 @@ | |||
1157 | 39 | maaslog = get_maas_logger("websockets.device") | 46 | maaslog = get_maas_logger("websockets.device") |
1158 | 40 | 47 | ||
1159 | 41 | 48 | ||
1160 | 42 | class DEVICE_IP_ASSIGNMENT: | ||
1161 | 43 | """The vocabulary of a `Device`'s possible IP assignment type. This value | ||
1162 | 44 | is calculated by looking at the overall model for a `Device`. This is not | ||
1163 | 45 | set directly on the model.""" | ||
1164 | 46 | #: Device is outside of MAAS control. | ||
1165 | 47 | EXTERNAL = "external" | ||
1166 | 48 | |||
1167 | 49 | #: Device receives ip address from `NodeGroupInterface` dynamic range. | ||
1168 | 50 | DYNAMIC = "dynamic" | ||
1169 | 51 | |||
1170 | 52 | #: Device has ip address assigned from `NodeGroupInterface` static range. | ||
1171 | 53 | STATIC = "static" | ||
1172 | 54 | |||
1173 | 55 | |||
1174 | 56 | def get_Interface_from_list(interfaces, mac): | 49 | def get_Interface_from_list(interfaces, mac): |
1175 | 57 | """Return the `Interface` object with the given MAC address.""" | 50 | """Return the `Interface` object with the given MAC address.""" |
1176 | 58 | # Compare using EUI instances so that we're not concerned with a MAC's | 51 | # Compare using EUI instances so that we're not concerned with a MAC's |
1177 | @@ -77,6 +70,12 @@ | |||
1178 | 77 | 'set_active', | 70 | 'set_active', |
1179 | 78 | 'create', | 71 | 'create', |
1180 | 79 | 'create_interface', | 72 | 'create_interface', |
1181 | 73 | 'create_physical', | ||
1182 | 74 | 'update_interface', | ||
1183 | 75 | 'delete_interface', | ||
1184 | 76 | 'link_subnet', | ||
1185 | 77 | 'unlink_subnet', | ||
1186 | 78 | 'update', | ||
1187 | 80 | 'action'] | 79 | 'action'] |
1188 | 81 | exclude = [ | 80 | exclude = [ |
1189 | 82 | "creation_type", | 81 | "creation_type", |
1190 | @@ -149,32 +148,39 @@ | |||
1191 | 149 | 148 | ||
1192 | 150 | def dehydrate(self, obj, data, for_list=False): | 149 | def dehydrate(self, obj, data, for_list=False): |
1193 | 151 | """Add extra fields to `data`.""" | 150 | """Add extra fields to `data`.""" |
1197 | 152 | data["fqdn"] = obj.fqdn | 151 | data = super().dehydrate(obj, data, for_list=for_list) |
1195 | 153 | data["actions"] = list(compile_node_actions(obj, self.user).keys()) | ||
1196 | 154 | data["node_type_display"] = obj.get_node_type_display() | ||
1198 | 155 | 152 | ||
1199 | 153 | # We handle interfaces ourselves, because of ip_assignment. | ||
1200 | 156 | boot_interface = obj.get_boot_interface() | 154 | boot_interface = obj.get_boot_interface() |
1201 | 157 | data["primary_mac"] = ( | 155 | data["primary_mac"] = ( |
1202 | 158 | "%s" % boot_interface.mac_address | 156 | "%s" % boot_interface.mac_address |
1203 | 159 | if boot_interface is not None else "") | 157 | if boot_interface is not None else "") |
1222 | 160 | data["extra_macs"] = [ | 158 | |
1223 | 161 | "%s" % interface.mac_address | 159 | if for_list: |
1224 | 162 | for interface in obj.interface_set.all() | 160 | data["ip_assignment"] = self.dehydrate_ip_assignment( |
1225 | 163 | if interface != boot_interface | 161 | obj, boot_interface) |
1226 | 164 | ] | 162 | data["ip_address"] = self.dehydrate_ip_address(obj, boot_interface) |
1227 | 165 | 163 | else: | |
1228 | 166 | data["ip_assignment"] = self.dehydrate_ip_assignment( | 164 | data["interfaces"] = [ |
1229 | 167 | obj, boot_interface) | 165 | self.dehydrate_interface(interface, obj) |
1230 | 168 | data["ip_address"] = self.dehydrate_ip_address( | 166 | for interface in obj.interface_set.all().order_by('name') |
1231 | 169 | obj, boot_interface) | 167 | ] |
1232 | 170 | 168 | # Propogate the boot interface ip assignment/address to the device. | |
1233 | 171 | data["tags"] = [ | 169 | for iface_data in data["interfaces"]: |
1234 | 172 | tag.name | 170 | if iface_data["name"] == boot_interface.name: |
1235 | 173 | for tag in obj.tags.all() | 171 | data["ip_assignment"] = iface_data["ip_assignment"] |
1236 | 174 | ] | 172 | data["ip_address"] = iface_data["ip_address"] |
1237 | 175 | return data | 173 | |
1238 | 176 | 174 | return data | |
1239 | 177 | def _get_first_none_discovered_ip(self, ip_addresses): | 175 | |
1240 | 176 | def dehydrate_interface(self, interface, obj): | ||
1241 | 177 | """Add extra fields to interface data.""" | ||
1242 | 178 | data = super().dehydrate_interface(interface, obj) | ||
1243 | 179 | data["ip_assignment"] = self.dehydrate_ip_assignment(obj, interface) | ||
1244 | 180 | data["ip_address"] = self.dehydrate_ip_address(obj, interface) | ||
1245 | 181 | return data | ||
1246 | 182 | |||
1247 | 183 | def _get_first_non_discovered_ip(self, ip_addresses): | ||
1248 | 178 | for ip in ip_addresses: | 184 | for ip in ip_addresses: |
1249 | 179 | if ip.alloc_type != IPADDRESS_TYPE.DISCOVERED: | 185 | if ip.alloc_type != IPADDRESS_TYPE.DISCOVERED: |
1250 | 180 | return ip | 186 | return ip |
1251 | @@ -190,15 +196,15 @@ | |||
1252 | 190 | return "" | 196 | return "" |
1253 | 191 | # We get the IP address from the all() so the cache is used. | 197 | # We get the IP address from the all() so the cache is used. |
1254 | 192 | ip_addresses = list(interface.ip_addresses.all()) | 198 | ip_addresses = list(interface.ip_addresses.all()) |
1256 | 193 | first_ip = self._get_first_none_discovered_ip(ip_addresses) | 199 | first_ip = self._get_first_non_discovered_ip(ip_addresses) |
1257 | 194 | if first_ip is not None: | 200 | if first_ip is not None: |
1258 | 195 | if first_ip.alloc_type == IPADDRESS_TYPE.DHCP: | 201 | if first_ip.alloc_type == IPADDRESS_TYPE.DHCP: |
1260 | 196 | return DEVICE_IP_ASSIGNMENT.DYNAMIC | 202 | return DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC |
1261 | 197 | elif first_ip.subnet is None: | 203 | elif first_ip.subnet is None: |
1263 | 198 | return DEVICE_IP_ASSIGNMENT.EXTERNAL | 204 | return DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL |
1264 | 199 | else: | 205 | else: |
1267 | 200 | return DEVICE_IP_ASSIGNMENT.STATIC | 206 | return DEVICE_IP_ASSIGNMENT_TYPE.STATIC |
1268 | 201 | return DEVICE_IP_ASSIGNMENT.DYNAMIC | 207 | return DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC |
1269 | 202 | 208 | ||
1270 | 203 | def dehydrate_ip_address(self, obj, interface): | 209 | def dehydrate_ip_address(self, obj, interface): |
1271 | 204 | """Return the IP address for the device.""" | 210 | """Return the IP address for the device.""" |
1272 | @@ -207,7 +213,7 @@ | |||
1273 | 207 | 213 | ||
1274 | 208 | # Get ip address from StaticIPAddress if available. | 214 | # Get ip address from StaticIPAddress if available. |
1275 | 209 | ip_addresses = list(interface.ip_addresses.all()) | 215 | ip_addresses = list(interface.ip_addresses.all()) |
1277 | 210 | first_ip = self._get_first_none_discovered_ip(ip_addresses) | 216 | first_ip = self._get_first_non_discovered_ip(ip_addresses) |
1278 | 211 | if first_ip is not None: | 217 | if first_ip is not None: |
1279 | 212 | if first_ip.alloc_type == IPADDRESS_TYPE.DHCP: | 218 | if first_ip.alloc_type == IPADDRESS_TYPE.DHCP: |
1280 | 213 | discovered_ip = self._get_first_discovered_ip_with_ip( | 219 | discovered_ip = self._get_first_discovered_ip_with_ip( |
1281 | @@ -254,6 +260,8 @@ | |||
1282 | 254 | "parent": params.get("parent"), | 260 | "parent": params.get("parent"), |
1283 | 255 | } | 261 | } |
1284 | 256 | 262 | ||
1285 | 263 | if "zone" in params: | ||
1286 | 264 | new_params["zone"] = params["zone"]["name"] | ||
1287 | 257 | if "domain" in params: | 265 | if "domain" in params: |
1288 | 258 | new_params["domain"] = params["domain"]["name"] | 266 | new_params["domain"] = params["domain"]["name"] |
1289 | 259 | 267 | ||
1290 | @@ -268,16 +276,17 @@ | |||
1291 | 268 | def _configure_interface(self, interface, params): | 276 | def _configure_interface(self, interface, params): |
1292 | 269 | """Configure the interface based on the selection.""" | 277 | """Configure the interface based on the selection.""" |
1293 | 270 | ip_assignment = params["ip_assignment"] | 278 | ip_assignment = params["ip_assignment"] |
1295 | 271 | if ip_assignment == DEVICE_IP_ASSIGNMENT.EXTERNAL: | 279 | interface.ip_addresses.all().delete() |
1296 | 280 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL: | ||
1297 | 272 | subnet = Subnet.objects.get_best_subnet_for_ip( | 281 | subnet = Subnet.objects.get_best_subnet_for_ip( |
1298 | 273 | params["ip_address"]) | 282 | params["ip_address"]) |
1299 | 274 | sticky_ip = StaticIPAddress.objects.create( | 283 | sticky_ip = StaticIPAddress.objects.create( |
1300 | 275 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, | 284 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, |
1301 | 276 | ip=params["ip_address"], subnet=subnet, user=self.user) | 285 | ip=params["ip_address"], subnet=subnet, user=self.user) |
1302 | 277 | interface.ip_addresses.add(sticky_ip) | 286 | interface.ip_addresses.add(sticky_ip) |
1304 | 278 | elif ip_assignment == DEVICE_IP_ASSIGNMENT.DYNAMIC: | 287 | elif ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: |
1305 | 279 | interface.link_subnet(INTERFACE_LINK_TYPE.DHCP, None) | 288 | interface.link_subnet(INTERFACE_LINK_TYPE.DHCP, None) |
1307 | 280 | elif ip_assignment == DEVICE_IP_ASSIGNMENT.STATIC: | 289 | elif ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: |
1308 | 281 | # Convert an empty string to None. | 290 | # Convert an empty string to None. |
1309 | 282 | ip_address = params.get("ip_address") | 291 | ip_address = params.get("ip_address") |
1310 | 283 | if not ip_address: | 292 | if not ip_address: |
1311 | @@ -316,6 +325,76 @@ | |||
1312 | 316 | else: | 325 | else: |
1313 | 317 | raise HandlerValidationError(form.errors) | 326 | raise HandlerValidationError(form.errors) |
1314 | 318 | 327 | ||
1315 | 328 | def create_physical(self, params): | ||
1316 | 329 | """Create a physical interface, an alias for create_interface.""" | ||
1317 | 330 | return self.create_interface(params) | ||
1318 | 331 | |||
1319 | 332 | def update_interface(self, params): | ||
1320 | 333 | """Update the interface.""" | ||
1321 | 334 | # Only admin users can perform update. | ||
1322 | 335 | if not reload_object(self.user).is_superuser: | ||
1323 | 336 | raise HandlerPermissionError() | ||
1324 | 337 | |||
1325 | 338 | device = self.get_object(params) | ||
1326 | 339 | interface = Interface.objects.get( | ||
1327 | 340 | node=device, id=params["interface_id"]) | ||
1328 | 341 | interface_form = InterfaceForm.get_interface_form(interface.type) | ||
1329 | 342 | form = interface_form(instance=interface, data=params) | ||
1330 | 343 | if form.is_valid(): | ||
1331 | 344 | interface = form.save() | ||
1332 | 345 | self._configure_interface(interface, params) | ||
1333 | 346 | return self.full_dehydrate(reload_object(device)) | ||
1334 | 347 | else: | ||
1335 | 348 | raise ValidationError(form.errors) | ||
1336 | 349 | |||
1337 | 350 | def delete_interface(self, params): | ||
1338 | 351 | """Delete the interface.""" | ||
1339 | 352 | # Only admin users can perform delete. | ||
1340 | 353 | if not reload_object(self.user).is_superuser: | ||
1341 | 354 | raise HandlerPermissionError() | ||
1342 | 355 | |||
1343 | 356 | node = self.get_object(params) | ||
1344 | 357 | interface = Interface.objects.get(node=node, id=params["interface_id"]) | ||
1345 | 358 | interface.delete() | ||
1346 | 359 | |||
1347 | 360 | def link_subnet(self, params): | ||
1348 | 361 | """Create or update the link.""" | ||
1349 | 362 | # Only admin users can perform update. | ||
1350 | 363 | if not reload_object(self.user).is_superuser: | ||
1351 | 364 | raise HandlerPermissionError() | ||
1352 | 365 | |||
1353 | 366 | node = self.get_object(params) | ||
1354 | 367 | interface = Interface.objects.get(node=node, id=params["interface_id"]) | ||
1355 | 368 | if params['ip_assignment'] == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
1356 | 369 | mode = INTERFACE_LINK_TYPE.STATIC | ||
1357 | 370 | elif params['ip_assignment'] == DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: | ||
1358 | 371 | mode = INTERFACE_LINK_TYPE.DHCP | ||
1359 | 372 | else: | ||
1360 | 373 | mode = INTERFACE_LINK_TYPE.LINK_UP | ||
1361 | 374 | subnet = None | ||
1362 | 375 | if "subnet" in params: | ||
1363 | 376 | subnet = Subnet.objects.get(id=params["subnet"]) | ||
1364 | 377 | if "link_id" in params: | ||
1365 | 378 | # We are updating an already existing link. | ||
1366 | 379 | interface.update_link_by_id( | ||
1367 | 380 | params["link_id"], mode, subnet, | ||
1368 | 381 | ip_address=params.get("ip_address", None)) | ||
1369 | 382 | elif params['ip_assignment'] == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
1370 | 383 | # We are creating a new link. | ||
1371 | 384 | interface.link_subnet( | ||
1372 | 385 | INTERFACE_LINK_TYPE.STATIC, subnet, | ||
1373 | 386 | ip_address=params.get("ip_address", None)) | ||
1374 | 387 | |||
1375 | 388 | def unlink_subnet(self, params): | ||
1376 | 389 | """Delete the link.""" | ||
1377 | 390 | # Only admin users can perform unlink. | ||
1378 | 391 | if not reload_object(self.user).is_superuser: | ||
1379 | 392 | raise HandlerPermissionError() | ||
1380 | 393 | |||
1381 | 394 | node = self.get_object(params) | ||
1382 | 395 | interface = Interface.objects.get(node=node, id=params["interface_id"]) | ||
1383 | 396 | interface.unlink_subnet_by_id(params["link_id"]) | ||
1384 | 397 | |||
1385 | 319 | def action(self, params): | 398 | def action(self, params): |
1386 | 320 | """Perform the action on the object.""" | 399 | """Perform the action on the object.""" |
1387 | 321 | obj = self.get_object(params) | 400 | obj = self.get_object(params) |
1388 | @@ -327,3 +406,11 @@ | |||
1389 | 327 | "%s action is not available for this device." % action_name) | 406 | "%s action is not available for this device." % action_name) |
1390 | 328 | extra_params = params.get("extra", {}) | 407 | extra_params = params.get("extra", {}) |
1391 | 329 | return action.execute(**extra_params) | 408 | return action.execute(**extra_params) |
1392 | 409 | |||
1393 | 410 | def update(self, params): | ||
1394 | 411 | """Update the object from params.""" | ||
1395 | 412 | # Only admin users can perform update. | ||
1396 | 413 | if not reload_object(self.user).is_superuser: | ||
1397 | 414 | raise HandlerPermissionError() | ||
1398 | 415 | |||
1399 | 416 | return super().update(params) | ||
1400 | 330 | 417 | ||
1401 | === modified file 'src/maasserver/websockets/handlers/node.py' | |||
1402 | --- src/maasserver/websockets/handlers/node.py 2017-02-17 14:23:04 +0000 | |||
1403 | +++ src/maasserver/websockets/handlers/node.py 2017-02-28 04:16:58 +0000 | |||
1404 | @@ -16,6 +16,7 @@ | |||
1405 | 16 | FILESYSTEM_FORMAT_TYPE_CHOICES, | 16 | FILESYSTEM_FORMAT_TYPE_CHOICES, |
1406 | 17 | FILESYSTEM_FORMAT_TYPE_CHOICES_DICT, | 17 | FILESYSTEM_FORMAT_TYPE_CHOICES_DICT, |
1407 | 18 | NODE_STATUS, | 18 | NODE_STATUS, |
1408 | 19 | NODE_TYPE, | ||
1409 | 19 | ) | 20 | ) |
1410 | 20 | from maasserver.models.cacheset import CacheSet | 21 | from maasserver.models.cacheset import CacheSet |
1411 | 21 | from maasserver.models.config import Config | 22 | from maasserver.models.config import Config |
1412 | @@ -99,36 +100,13 @@ | |||
1413 | 99 | def dehydrate(self, obj, data, for_list=False): | 100 | def dehydrate(self, obj, data, for_list=False): |
1414 | 100 | """Add extra fields to `data`.""" | 101 | """Add extra fields to `data`.""" |
1415 | 101 | data["fqdn"] = obj.fqdn | 102 | data["fqdn"] = obj.fqdn |
1416 | 102 | data["status"] = obj.display_status() | ||
1417 | 103 | data["status_code"] = obj.status | ||
1418 | 104 | data["actions"] = list(compile_node_actions(obj, self.user).keys()) | 103 | data["actions"] = list(compile_node_actions(obj, self.user).keys()) |
1419 | 105 | data["memory"] = obj.display_memory() | ||
1420 | 106 | data["node_type_display"] = obj.get_node_type_display() | 104 | data["node_type_display"] = obj.get_node_type_display() |
1421 | 107 | 105 | ||
1422 | 108 | data["extra_macs"] = [ | 106 | data["extra_macs"] = [ |
1423 | 109 | "%s" % mac_address | 107 | "%s" % mac_address |
1424 | 110 | for mac_address in obj.get_extra_macs() | 108 | for mac_address in obj.get_extra_macs() |
1425 | 111 | ] | 109 | ] |
1426 | 112 | boot_interface = obj.get_boot_interface() | ||
1427 | 113 | if boot_interface is not None: | ||
1428 | 114 | data["pxe_mac"] = "%s" % boot_interface.mac_address | ||
1429 | 115 | data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor() | ||
1430 | 116 | else: | ||
1431 | 117 | data["pxe_mac"] = data["pxe_mac_vendor"] = "" | ||
1432 | 118 | |||
1433 | 119 | blockdevices = self.get_blockdevices_for(obj) | ||
1434 | 120 | physical_blockdevices = [ | ||
1435 | 121 | blockdevice for blockdevice in blockdevices | ||
1436 | 122 | if isinstance(blockdevice, PhysicalBlockDevice) | ||
1437 | 123 | ] | ||
1438 | 124 | data["physical_disk_count"] = len(physical_blockdevices) | ||
1439 | 125 | data["storage"] = "%3.1f" % ( | ||
1440 | 126 | sum([ | ||
1441 | 127 | blockdevice.size | ||
1442 | 128 | for blockdevice in physical_blockdevices | ||
1443 | 129 | ]) / (1000 ** 3)) | ||
1444 | 130 | data["storage_tags"] = self.get_all_storage_tags(blockdevices) | ||
1445 | 131 | |||
1446 | 132 | subnets = self.get_all_subnets(obj) | 110 | subnets = self.get_all_subnets(obj) |
1447 | 133 | data["subnets"] = [subnet.cidr for subnet in subnets] | 111 | data["subnets"] = [subnet.cidr for subnet in subnets] |
1448 | 134 | data["fabrics"] = self.get_all_fabric_names(obj, subnets) | 112 | data["fabrics"] = self.get_all_fabric_names(obj, subnets) |
1449 | @@ -138,73 +116,100 @@ | |||
1450 | 138 | tag.name | 116 | tag.name |
1451 | 139 | for tag in obj.tags.all() | 117 | for tag in obj.tags.all() |
1452 | 140 | ] | 118 | ] |
1458 | 141 | data["osystem"] = obj.get_osystem( | 119 | if obj.node_type != NODE_TYPE.DEVICE: |
1459 | 142 | default=self.default_osystem) | 120 | data["memory"] = obj.display_memory() |
1460 | 143 | data["distro_series"] = obj.get_distro_series( | 121 | data["status"] = obj.display_status() |
1461 | 144 | default=self.default_distro_series) | 122 | data["status_code"] = obj.status |
1462 | 145 | data["dhcp_on"] = self.get_providing_dhcp(obj) | 123 | boot_interface = obj.get_boot_interface() |
1463 | 124 | if boot_interface is not None: | ||
1464 | 125 | data["pxe_mac"] = "%s" % boot_interface.mac_address | ||
1465 | 126 | data["pxe_mac_vendor"] = obj.get_pxe_mac_vendor() | ||
1466 | 127 | else: | ||
1467 | 128 | data["pxe_mac"] = data["pxe_mac_vendor"] = "" | ||
1468 | 129 | |||
1469 | 130 | blockdevices = self.get_blockdevices_for(obj) | ||
1470 | 131 | physical_blockdevices = [ | ||
1471 | 132 | blockdevice for blockdevice in blockdevices | ||
1472 | 133 | if isinstance(blockdevice, PhysicalBlockDevice) | ||
1473 | 134 | ] | ||
1474 | 135 | data["physical_disk_count"] = len(physical_blockdevices) | ||
1475 | 136 | data["storage"] = "%3.1f" % ( | ||
1476 | 137 | sum( | ||
1477 | 138 | blockdevice.size | ||
1478 | 139 | for blockdevice in physical_blockdevices | ||
1479 | 140 | ) / (1000 ** 3)) | ||
1480 | 141 | data["storage_tags"] = self.get_all_storage_tags(blockdevices) | ||
1481 | 142 | |||
1482 | 143 | data["osystem"] = obj.get_osystem( | ||
1483 | 144 | default=self.default_osystem) | ||
1484 | 145 | data["distro_series"] = obj.get_distro_series( | ||
1485 | 146 | default=self.default_distro_series) | ||
1486 | 147 | data["dhcp_on"] = self.get_providing_dhcp(obj) | ||
1487 | 146 | if not for_list: | 148 | if not for_list: |
1488 | 147 | data["hwe_kernel"] = make_hwe_kernel_ui_text(obj.hwe_kernel) | ||
1489 | 148 | |||
1490 | 149 | data["power_type"] = obj.power_type | ||
1491 | 150 | data["power_parameters"] = self.dehydrate_power_parameters( | ||
1492 | 151 | obj.power_parameters) | ||
1493 | 152 | data["power_bmc_node_count"] = obj.bmc.node_set.count() if ( | ||
1494 | 153 | obj.bmc is not None) else 0 | ||
1495 | 154 | |||
1496 | 155 | # Network | ||
1497 | 156 | data["interfaces"] = [ | ||
1498 | 157 | self.dehydrate_interface(interface, obj) | ||
1499 | 158 | for interface in obj.interface_set.all().order_by('name') | ||
1500 | 159 | ] | ||
1501 | 160 | data["on_network"] = obj.on_network() | 149 | data["on_network"] = obj.on_network() |
1549 | 161 | 150 | if obj.node_type != NODE_TYPE.DEVICE: | |
1550 | 162 | # Storage | 151 | # XXX lamont 2017-02-15 Much of this should be split out into |
1551 | 163 | data["disks"] = sorted(chain( | 152 | # individual methods, rather than having this huge block of |
1552 | 164 | (self.dehydrate_blockdevice(blockdevice, obj) | 153 | # dense code here. |
1553 | 165 | for blockdevice in blockdevices), | 154 | # Network |
1554 | 166 | (self.dehydrate_volume_group(volume_group) for volume_group | 155 | data["interfaces"] = [ |
1555 | 167 | in VolumeGroup.objects.filter_by_node(obj)), | 156 | self.dehydrate_interface(interface, obj) |
1556 | 168 | (self.dehydrate_cache_set(cache_set) for cache_set | 157 | for interface in obj.interface_set.all().order_by('name') |
1557 | 169 | in CacheSet.objects.get_cache_sets_for_node(obj)), | 158 | ] |
1558 | 170 | ), key=itemgetter("name")) | 159 | |
1559 | 171 | data["supported_filesystems"] = [ | 160 | data["hwe_kernel"] = make_hwe_kernel_ui_text(obj.hwe_kernel) |
1560 | 172 | {'key': key, 'ui': ui} | 161 | |
1561 | 173 | for key, ui in FILESYSTEM_FORMAT_TYPE_CHOICES | 162 | data["power_type"] = obj.power_type |
1562 | 174 | ] | 163 | data["power_parameters"] = self.dehydrate_power_parameters( |
1563 | 175 | data["storage_layout_issues"] = obj.storage_layout_issues() | 164 | obj.power_parameters) |
1564 | 176 | data["special_filesystems"] = [ | 165 | data["power_bmc_node_count"] = obj.bmc.node_set.count() if ( |
1565 | 177 | self.dehydrate_filesystem(filesystem) | 166 | obj.bmc is not None) else 0 |
1566 | 178 | for filesystem in obj.special_filesystems.order_by("id") | 167 | |
1567 | 179 | ] | 168 | # Storage |
1568 | 180 | 169 | data["disks"] = sorted(chain( | |
1569 | 181 | # Events | 170 | (self.dehydrate_blockdevice(blockdevice, obj) |
1570 | 182 | data["events"] = self.dehydrate_events(obj) | 171 | for blockdevice in blockdevices), |
1571 | 183 | 172 | (self.dehydrate_volume_group(volume_group) for volume_group | |
1572 | 184 | # Machine output | 173 | in VolumeGroup.objects.filter_by_node(obj)), |
1573 | 185 | data = self.dehydrate_summary_output(obj, data) | 174 | (self.dehydrate_cache_set(cache_set) for cache_set |
1574 | 186 | # XXX ltrager 2017-01-27 - Show the testing results in the | 175 | in CacheSet.objects.get_cache_sets_for_node(obj)), |
1575 | 187 | # commissioning table until we get the testing UI done. | 176 | ), key=itemgetter("name")) |
1576 | 188 | if obj.current_testing_script_set is not None: | 177 | data["supported_filesystems"] = [ |
1577 | 189 | data["commissioning_results"] = self.dehydrate_script_set( | 178 | {'key': key, 'ui': ui} |
1578 | 190 | chain( | 179 | for key, ui in FILESYSTEM_FORMAT_TYPE_CHOICES |
1579 | 191 | obj.current_commissioning_script_set, | 180 | ] |
1580 | 192 | obj.current_testing_script_set, | 181 | data["storage_layout_issues"] = obj.storage_layout_issues() |
1581 | 193 | )) | 182 | data["special_filesystems"] = [ |
1582 | 194 | else: | 183 | self.dehydrate_filesystem(filesystem) |
1583 | 195 | data["commissioning_results"] = self.dehydrate_script_set( | 184 | for filesystem in obj.special_filesystems.order_by("id") |
1584 | 196 | obj.current_commissioning_script_set) | 185 | ] |
1585 | 197 | data["installation_results"] = self.dehydrate_script_set( | 186 | |
1586 | 198 | obj.current_installation_script_set) | 187 | # Events |
1587 | 199 | 188 | data["events"] = self.dehydrate_events(obj) | |
1588 | 200 | # Third party drivers | 189 | |
1589 | 201 | if Config.objects.get_config('enable_third_party_drivers'): | 190 | # Machine output |
1590 | 202 | driver = get_third_party_driver(obj) | 191 | data = self.dehydrate_summary_output(obj, data) |
1591 | 203 | if "module" in driver and "comment" in driver: | 192 | # XXX ltrager 2017-01-27 - Show the testing results in the |
1592 | 204 | data["third_party_driver"] = { | 193 | # commissioning table until we get the testing UI done. |
1593 | 205 | "module": driver["module"], | 194 | if obj.current_testing_script_set is not None: |
1594 | 206 | "comment": driver["comment"], | 195 | data["commissioning_results"] = self.dehydrate_script_set( |
1595 | 207 | } | 196 | chain( |
1596 | 197 | obj.current_commissioning_script_set, | ||
1597 | 198 | obj.current_testing_script_set, | ||
1598 | 199 | )) | ||
1599 | 200 | else: | ||
1600 | 201 | data["commissioning_results"] = self.dehydrate_script_set( | ||
1601 | 202 | obj.current_commissioning_script_set) | ||
1602 | 203 | data["installation_results"] = self.dehydrate_script_set( | ||
1603 | 204 | obj.current_installation_script_set) | ||
1604 | 205 | # Third party drivers | ||
1605 | 206 | if Config.objects.get_config('enable_third_party_drivers'): | ||
1606 | 207 | driver = get_third_party_driver(obj) | ||
1607 | 208 | if "module" in driver and "comment" in driver: | ||
1608 | 209 | data["third_party_driver"] = { | ||
1609 | 210 | "module": driver["module"], | ||
1610 | 211 | "comment": driver["comment"], | ||
1611 | 212 | } | ||
1612 | 208 | 213 | ||
1613 | 209 | return data | 214 | return data |
1614 | 210 | 215 | ||
1615 | @@ -495,8 +500,8 @@ | |||
1616 | 495 | def get_all_space_names(self, subnets): | 500 | def get_all_space_names(self, subnets): |
1617 | 496 | space_names = set() | 501 | space_names = set() |
1618 | 497 | for subnet in subnets: | 502 | for subnet in subnets: |
1621 | 498 | if subnet.space is not None: | 503 | if subnet.vlan.space is not None: |
1622 | 499 | space_names.add(subnet.space.name) | 504 | space_names.add(subnet.vlan.space.name) |
1623 | 500 | return list(space_names) | 505 | return list(space_names) |
1624 | 501 | 506 | ||
1625 | 502 | def get_blockdevices_for(self, obj): | 507 | def get_blockdevices_for(self, obj): |
1626 | 503 | 508 | ||
1627 | === modified file 'src/maasserver/websockets/handlers/tests/test_device.py' | |||
1628 | --- src/maasserver/websockets/handlers/tests/test_device.py 2017-01-28 00:51:47 +0000 | |||
1629 | +++ src/maasserver/websockets/handlers/tests/test_device.py 2017-02-28 04:16:58 +0000 | |||
1630 | @@ -5,7 +5,15 @@ | |||
1631 | 5 | 5 | ||
1632 | 6 | __all__ = [] | 6 | __all__ = [] |
1633 | 7 | 7 | ||
1634 | 8 | from operator import itemgetter | ||
1635 | 9 | import random | ||
1636 | 10 | from unittest.mock import ANY | ||
1637 | 11 | |||
1638 | 8 | from maasserver.enum import ( | 12 | from maasserver.enum import ( |
1639 | 13 | DEVICE_IP_ASSIGNMENT_TYPE, | ||
1640 | 14 | DEVICE_IP_ASSIGNMENT_TYPE_CHOICES, | ||
1641 | 15 | INTERFACE_LINK_TYPE, | ||
1642 | 16 | INTERFACE_TYPE, | ||
1643 | 9 | IPADDRESS_TYPE, | 17 | IPADDRESS_TYPE, |
1644 | 10 | NODE_TYPE, | 18 | NODE_TYPE, |
1645 | 11 | ) | 19 | ) |
1646 | @@ -28,13 +36,17 @@ | |||
1647 | 28 | dehydrate_datetime, | 36 | dehydrate_datetime, |
1648 | 29 | HandlerDoesNotExistError, | 37 | HandlerDoesNotExistError, |
1649 | 30 | HandlerError, | 38 | HandlerError, |
1650 | 39 | HandlerPermissionError, | ||
1651 | 31 | HandlerValidationError, | 40 | HandlerValidationError, |
1652 | 32 | ) | 41 | ) |
1657 | 33 | from maasserver.websockets.handlers.device import ( | 42 | from maasserver.websockets.handlers.device import DeviceHandler |
1654 | 34 | DEVICE_IP_ASSIGNMENT, | ||
1655 | 35 | DeviceHandler, | ||
1656 | 36 | ) | ||
1658 | 37 | from maastesting.djangotestcase import count_queries | 43 | from maastesting.djangotestcase import count_queries |
1659 | 44 | from maastesting.matchers import MockCalledOnceWith | ||
1660 | 45 | from maastesting.matchers import MockNotCalled | ||
1661 | 46 | from netaddr import ( | ||
1662 | 47 | IPAddress, | ||
1663 | 48 | IPNetwork, | ||
1664 | 49 | ) | ||
1665 | 38 | from testtools import ExpectedException | 50 | from testtools import ExpectedException |
1666 | 39 | from testtools.matchers import ( | 51 | from testtools.matchers import ( |
1667 | 40 | Equals, | 52 | Equals, |
1668 | @@ -44,27 +56,25 @@ | |||
1669 | 44 | 56 | ||
1670 | 45 | class TestDeviceHandler(MAASTransactionServerTestCase): | 57 | class TestDeviceHandler(MAASTransactionServerTestCase): |
1671 | 46 | 58 | ||
1675 | 47 | def dehydrate_ip_assignment(self, device): | 59 | def dehydrate_ip_assignment(self, device, interface): |
1676 | 48 | boot_interface = device.get_boot_interface() | 60 | if interface is None: |
1674 | 49 | if boot_interface is None: | ||
1677 | 50 | return "" | 61 | return "" |
1679 | 51 | ip_address = boot_interface.ip_addresses.exclude( | 62 | ip_address = interface.ip_addresses.exclude( |
1680 | 52 | alloc_type=IPADDRESS_TYPE.DISCOVERED).first() | 63 | alloc_type=IPADDRESS_TYPE.DISCOVERED).first() |
1681 | 53 | if ip_address is not None: | 64 | if ip_address is not None: |
1682 | 54 | if ip_address.alloc_type == IPADDRESS_TYPE.DHCP: | 65 | if ip_address.alloc_type == IPADDRESS_TYPE.DHCP: |
1684 | 55 | return DEVICE_IP_ASSIGNMENT.DYNAMIC | 66 | return DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC |
1685 | 56 | elif ip_address.subnet is None: | 67 | elif ip_address.subnet is None: |
1687 | 57 | return DEVICE_IP_ASSIGNMENT.EXTERNAL | 68 | return DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL |
1688 | 58 | else: | 69 | else: |
1691 | 59 | return DEVICE_IP_ASSIGNMENT.STATIC | 70 | return DEVICE_IP_ASSIGNMENT_TYPE.STATIC |
1692 | 60 | return DEVICE_IP_ASSIGNMENT.DYNAMIC | 71 | return DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC |
1693 | 61 | 72 | ||
1695 | 62 | def dehydrate_ip_address(self, device): | 73 | def dehydrate_ip_address(self, device, interface): |
1696 | 63 | """Return the IP address for the device.""" | 74 | """Return the IP address for the device.""" |
1699 | 64 | boot_interface = device.get_boot_interface() | 75 | if interface is None: |
1698 | 65 | if boot_interface is None: | ||
1700 | 66 | return None | 76 | return None |
1702 | 67 | static_ip = boot_interface.ip_addresses.exclude( | 77 | static_ip = interface.ip_addresses.exclude( |
1703 | 68 | alloc_type=IPADDRESS_TYPE.DISCOVERED).first() | 78 | alloc_type=IPADDRESS_TYPE.DISCOVERED).first() |
1704 | 69 | if static_ip is not None: | 79 | if static_ip is not None: |
1705 | 70 | ip = static_ip.get_ip() | 80 | ip = static_ip.get_ip() |
1706 | @@ -72,8 +82,58 @@ | |||
1707 | 72 | return "%s" % ip | 82 | return "%s" % ip |
1708 | 73 | return None | 83 | return None |
1709 | 74 | 84 | ||
1710 | 85 | def dehydrate_interface(self, interface, obj): | ||
1711 | 86 | """Dehydrate a `interface` into a interface definition.""" | ||
1712 | 87 | # Sort the links by ID that way they show up in the same order in | ||
1713 | 88 | # the UI. | ||
1714 | 89 | links = sorted(interface.get_links(), key=itemgetter("id")) | ||
1715 | 90 | for link in links: | ||
1716 | 91 | # Replace the subnet object with the subnet_id. The client will | ||
1717 | 92 | # use this information to pull the subnet information from the | ||
1718 | 93 | # websocket. | ||
1719 | 94 | subnet = link.pop("subnet", None) | ||
1720 | 95 | if subnet is not None: | ||
1721 | 96 | link["subnet_id"] = subnet.id | ||
1722 | 97 | data = { | ||
1723 | 98 | "id": interface.id, | ||
1724 | 99 | "type": interface.type, | ||
1725 | 100 | "name": interface.get_name(), | ||
1726 | 101 | "enabled": interface.is_enabled(), | ||
1727 | 102 | "tags": interface.tags, | ||
1728 | 103 | "is_boot": interface == obj.get_boot_interface(), | ||
1729 | 104 | "mac_address": "%s" % interface.mac_address, | ||
1730 | 105 | "vlan_id": interface.vlan_id, | ||
1731 | 106 | "parents": [ | ||
1732 | 107 | nic.id | ||
1733 | 108 | for nic in interface.parents.all() | ||
1734 | 109 | ], | ||
1735 | 110 | "children": [ | ||
1736 | 111 | nic.child.id | ||
1737 | 112 | for nic in interface.children_relationships.all() | ||
1738 | 113 | ], | ||
1739 | 114 | "links": links, | ||
1740 | 115 | "ip_assignment": self.dehydrate_ip_assignment(obj, interface), | ||
1741 | 116 | "ip_address": self.dehydrate_ip_address(obj, interface), | ||
1742 | 117 | } | ||
1743 | 118 | return data | ||
1744 | 119 | |||
1745 | 75 | def dehydrate_device(self, node, user, for_list=False): | 120 | def dehydrate_device(self, node, user, for_list=False): |
1746 | 76 | boot_interface = node.get_boot_interface() | 121 | boot_interface = node.get_boot_interface() |
1747 | 122 | subnets = set( | ||
1748 | 123 | ip_address.subnet | ||
1749 | 124 | for interface in node.interface_set.all() | ||
1750 | 125 | for ip_address in interface.ip_addresses.all() | ||
1751 | 126 | if ip_address.subnet is not None) | ||
1752 | 127 | space_names = set( | ||
1753 | 128 | subnet.space.name | ||
1754 | 129 | for subnet in subnets | ||
1755 | 130 | if subnet.space is not None) | ||
1756 | 131 | fabric_names = set( | ||
1757 | 132 | iface.vlan.fabric.name | ||
1758 | 133 | for iface in node.interface_set.all() | ||
1759 | 134 | if iface.vlan is not None) | ||
1760 | 135 | fabric_names.update({subnet.vlan.fabric.name for subnet in subnets}) | ||
1761 | 136 | boot_interface = node.get_boot_interface() | ||
1762 | 77 | data = { | 137 | data = { |
1763 | 78 | "actions": list(compile_node_actions(node, user).keys()), | 138 | "actions": list(compile_node_actions(node, user).keys()), |
1764 | 79 | "bmc": node.bmc_id, | 139 | "bmc": node.bmc_id, |
1765 | @@ -95,8 +155,17 @@ | |||
1766 | 95 | "%s" % boot_interface.mac_address), | 155 | "%s" % boot_interface.mac_address), |
1767 | 96 | "parent": ( | 156 | "parent": ( |
1768 | 97 | node.parent.system_id if node.parent is not None else None), | 157 | node.parent.system_id if node.parent is not None else None), |
1771 | 98 | "ip_address": self.dehydrate_ip_address(node), | 158 | "ip_address": self.dehydrate_ip_address(node, boot_interface), |
1772 | 99 | "ip_assignment": self.dehydrate_ip_assignment(node), | 159 | "ip_assignment": self.dehydrate_ip_assignment( |
1773 | 160 | node, boot_interface), | ||
1774 | 161 | "interfaces": [ | ||
1775 | 162 | self.dehydrate_interface(interface, node) | ||
1776 | 163 | for interface in node.interface_set.all().order_by('name') | ||
1777 | 164 | ], | ||
1778 | 165 | "subnets": [subnet.cidr for subnet in subnets], | ||
1779 | 166 | "fabrics": list(fabric_names), | ||
1780 | 167 | "spaces": list(space_names), | ||
1781 | 168 | "on_network": node.on_network(), | ||
1782 | 100 | "owner": "" if node.owner is None else node.owner.username, | 169 | "owner": "" if node.owner is None else node.owner.username, |
1783 | 101 | "swap_size": node.swap_size, | 170 | "swap_size": node.swap_size, |
1784 | 102 | "system_id": node.system_id, | 171 | "system_id": node.system_id, |
1785 | @@ -121,6 +190,9 @@ | |||
1786 | 121 | "ip_address", | 190 | "ip_address", |
1787 | 122 | "ip_assignment", | 191 | "ip_assignment", |
1788 | 123 | "node_type_display", | 192 | "node_type_display", |
1789 | 193 | "subnets", | ||
1790 | 194 | "spaces", | ||
1791 | 195 | "fabrics", | ||
1792 | 124 | ] | 196 | ] |
1793 | 125 | for key in list(data): | 197 | for key in list(data): |
1794 | 126 | if key not in allowed_fields: | 198 | if key not in allowed_fields: |
1795 | @@ -133,20 +205,20 @@ | |||
1796 | 133 | for a device. This will setup the model to make sure the device will | 205 | for a device. This will setup the model to make sure the device will |
1797 | 134 | match `ip_assignment`.""" | 206 | match `ip_assignment`.""" |
1798 | 135 | if ip_assignment is None: | 207 | if ip_assignment is None: |
1800 | 136 | ip_assignment = factory.pick_enum(DEVICE_IP_ASSIGNMENT) | 208 | ip_assignment = factory.pick_enum(DEVICE_IP_ASSIGNMENT_TYPE) |
1801 | 137 | if owner is None: | 209 | if owner is None: |
1802 | 138 | owner = factory.make_User() | 210 | owner = factory.make_User() |
1803 | 139 | device = factory.make_Node( | 211 | device = factory.make_Node( |
1804 | 140 | node_type=NODE_TYPE.DEVICE, | 212 | node_type=NODE_TYPE.DEVICE, |
1805 | 141 | interface=True, owner=owner) | 213 | interface=True, owner=owner) |
1806 | 142 | interface = device.get_boot_interface() | 214 | interface = device.get_boot_interface() |
1808 | 143 | if ip_assignment == DEVICE_IP_ASSIGNMENT.EXTERNAL: | 215 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL: |
1809 | 144 | subnet = factory.make_Subnet() | 216 | subnet = factory.make_Subnet() |
1810 | 145 | factory.make_StaticIPAddress( | 217 | factory.make_StaticIPAddress( |
1811 | 146 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, | 218 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, |
1812 | 147 | ip=factory.pick_ip_in_network(subnet.get_ipnetwork()), | 219 | ip=factory.pick_ip_in_network(subnet.get_ipnetwork()), |
1813 | 148 | subnet=subnet, user=owner) | 220 | subnet=subnet, user=owner) |
1815 | 149 | elif ip_assignment == DEVICE_IP_ASSIGNMENT.DYNAMIC: | 221 | elif ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: |
1816 | 150 | factory.make_StaticIPAddress( | 222 | factory.make_StaticIPAddress( |
1817 | 151 | alloc_type=IPADDRESS_TYPE.DHCP, ip="", interface=interface) | 223 | alloc_type=IPADDRESS_TYPE.DHCP, ip="", interface=interface) |
1818 | 152 | else: | 224 | else: |
1819 | @@ -206,7 +278,7 @@ | |||
1820 | 206 | handler.list({})) | 278 | handler.list({})) |
1821 | 207 | 279 | ||
1822 | 208 | @transactional | 280 | @transactional |
1824 | 209 | def test_list_num_queries_is_independent_of_num_devices(self): | 281 | def test_list_num_queries_is_the_expected_number(self): |
1825 | 210 | owner = factory.make_User() | 282 | owner = factory.make_User() |
1826 | 211 | handler = DeviceHandler(owner, {}) | 283 | handler = DeviceHandler(owner, {}) |
1827 | 212 | self.make_devices(10, owner=owner) | 284 | self.make_devices(10, owner=owner) |
1828 | @@ -222,6 +294,21 @@ | |||
1829 | 222 | self.assertEqual( | 294 | self.assertEqual( |
1830 | 223 | query_10_count, 12, | 295 | query_10_count, 12, |
1831 | 224 | "Number of queries has changed; make sure this is expected.") | 296 | "Number of queries has changed; make sure this is expected.") |
1832 | 297 | |||
1833 | 298 | @transactional | ||
1834 | 299 | def test_list_num_queries_is_independent_of_num_devices(self): | ||
1835 | 300 | owner = factory.make_User() | ||
1836 | 301 | handler = DeviceHandler(owner, {}) | ||
1837 | 302 | self.make_devices(10, owner=owner) | ||
1838 | 303 | query_10_count, _ = count_queries(handler.list, {}) | ||
1839 | 304 | self.make_devices(10, owner=owner) | ||
1840 | 305 | query_20_count, _ = count_queries(handler.list, {}) | ||
1841 | 306 | |||
1842 | 307 | # This check is to notify the developer that a change was made that | ||
1843 | 308 | # affects the number of queries performed when doing a node listing. | ||
1844 | 309 | # It is important to keep this number as low as possible. A larger | ||
1845 | 310 | # number means regiond has to do more work slowing down its process | ||
1846 | 311 | # and slowing down the client waiting for the response. | ||
1847 | 225 | self.assertEqual( | 312 | self.assertEqual( |
1848 | 226 | query_10_count, query_20_count, | 313 | query_10_count, query_20_count, |
1849 | 227 | "Number of queries is not independent to the number of nodes.") | 314 | "Number of queries is not independent to the number of nodes.") |
1850 | @@ -308,7 +395,7 @@ | |||
1851 | 308 | "primary_mac": mac, | 395 | "primary_mac": mac, |
1852 | 309 | "interfaces": [{ | 396 | "interfaces": [{ |
1853 | 310 | "mac": mac, | 397 | "mac": mac, |
1855 | 311 | "ip_assignment": DEVICE_IP_ASSIGNMENT.DYNAMIC, | 398 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC, |
1856 | 312 | }], | 399 | }], |
1857 | 313 | }) | 400 | }) |
1858 | 314 | self.expectThat(created_device["hostname"], Equals(hostname)) | 401 | self.expectThat(created_device["hostname"], Equals(hostname)) |
1859 | @@ -316,7 +403,7 @@ | |||
1860 | 316 | self.expectThat(created_device["extra_macs"], Equals([])) | 403 | self.expectThat(created_device["extra_macs"], Equals([])) |
1861 | 317 | self.expectThat( | 404 | self.expectThat( |
1862 | 318 | created_device["ip_assignment"], | 405 | created_device["ip_assignment"], |
1864 | 319 | Equals(DEVICE_IP_ASSIGNMENT.DYNAMIC)) | 406 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC)) |
1865 | 320 | self.expectThat(created_device["ip_address"], Is(None)) | 407 | self.expectThat(created_device["ip_address"], Is(None)) |
1866 | 321 | self.expectThat(created_device["owner"], Equals(user.username)) | 408 | self.expectThat(created_device["owner"], Equals(user.username)) |
1867 | 322 | 409 | ||
1868 | @@ -332,13 +419,13 @@ | |||
1869 | 332 | "primary_mac": mac, | 419 | "primary_mac": mac, |
1870 | 333 | "interfaces": [{ | 420 | "interfaces": [{ |
1871 | 334 | "mac": mac, | 421 | "mac": mac, |
1873 | 335 | "ip_assignment": DEVICE_IP_ASSIGNMENT.EXTERNAL, | 422 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL, |
1874 | 336 | "ip_address": ip_address, | 423 | "ip_address": ip_address, |
1875 | 337 | }], | 424 | }], |
1876 | 338 | }) | 425 | }) |
1877 | 339 | self.expectThat( | 426 | self.expectThat( |
1878 | 340 | created_device["ip_assignment"], | 427 | created_device["ip_assignment"], |
1880 | 341 | Equals(DEVICE_IP_ASSIGNMENT.EXTERNAL)) | 428 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL)) |
1881 | 342 | self.expectThat(created_device["ip_address"], Equals(ip_address)) | 429 | self.expectThat(created_device["ip_address"], Equals(ip_address)) |
1882 | 343 | self.expectThat( | 430 | self.expectThat( |
1883 | 344 | StaticIPAddress.objects.filter(ip=ip_address).count(), | 431 | StaticIPAddress.objects.filter(ip=ip_address).count(), |
1884 | @@ -356,13 +443,13 @@ | |||
1885 | 356 | "primary_mac": mac, | 443 | "primary_mac": mac, |
1886 | 357 | "interfaces": [{ | 444 | "interfaces": [{ |
1887 | 358 | "mac": mac, | 445 | "mac": mac, |
1889 | 359 | "ip_assignment": DEVICE_IP_ASSIGNMENT.STATIC, | 446 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.STATIC, |
1890 | 360 | "subnet": subnet.id, | 447 | "subnet": subnet.id, |
1891 | 361 | }], | 448 | }], |
1892 | 362 | }) | 449 | }) |
1893 | 363 | self.expectThat( | 450 | self.expectThat( |
1894 | 364 | created_device["ip_assignment"], | 451 | created_device["ip_assignment"], |
1896 | 365 | Equals(DEVICE_IP_ASSIGNMENT.STATIC)) | 452 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.STATIC)) |
1897 | 366 | static_interface = Interface.objects.get(mac_address=MAC(mac)) | 453 | static_interface = Interface.objects.get(mac_address=MAC(mac)) |
1898 | 367 | observed_subnet = static_interface.ip_addresses.first().subnet | 454 | observed_subnet = static_interface.ip_addresses.first().subnet |
1899 | 368 | self.expectThat( | 455 | self.expectThat( |
1900 | @@ -386,14 +473,14 @@ | |||
1901 | 386 | "primary_mac": mac, | 473 | "primary_mac": mac, |
1902 | 387 | "interfaces": [{ | 474 | "interfaces": [{ |
1903 | 388 | "mac": mac, | 475 | "mac": mac, |
1905 | 389 | "ip_assignment": DEVICE_IP_ASSIGNMENT.STATIC, | 476 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.STATIC, |
1906 | 390 | "subnet": subnet.id, | 477 | "subnet": subnet.id, |
1907 | 391 | "ip_address": ip_address, | 478 | "ip_address": ip_address, |
1908 | 392 | }], | 479 | }], |
1909 | 393 | }) | 480 | }) |
1910 | 394 | self.expectThat( | 481 | self.expectThat( |
1911 | 395 | created_device["ip_assignment"], | 482 | created_device["ip_assignment"], |
1913 | 396 | Equals(DEVICE_IP_ASSIGNMENT.STATIC)) | 483 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.STATIC)) |
1914 | 397 | self.expectThat(created_device["ip_address"], Equals(ip_address)) | 484 | self.expectThat(created_device["ip_address"], Equals(ip_address)) |
1915 | 398 | static_interface = Interface.objects.get(mac_address=MAC(mac)) | 485 | static_interface = Interface.objects.get(mac_address=MAC(mac)) |
1916 | 399 | observed_subnet = static_interface.ip_addresses.first().subnet | 486 | observed_subnet = static_interface.ip_addresses.first().subnet |
1917 | @@ -423,13 +510,13 @@ | |||
1918 | 423 | "interfaces": [ | 510 | "interfaces": [ |
1919 | 424 | { | 511 | { |
1920 | 425 | "mac": mac_static, | 512 | "mac": mac_static, |
1922 | 426 | "ip_assignment": DEVICE_IP_ASSIGNMENT.STATIC, | 513 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.STATIC, |
1923 | 427 | "subnet": subnet.id, | 514 | "subnet": subnet.id, |
1924 | 428 | "ip_address": static_ip_address, | 515 | "ip_address": static_ip_address, |
1925 | 429 | }, | 516 | }, |
1926 | 430 | { | 517 | { |
1927 | 431 | "mac": mac_external, | 518 | "mac": mac_external, |
1929 | 432 | "ip_assignment": DEVICE_IP_ASSIGNMENT.EXTERNAL, | 519 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL, |
1930 | 433 | "ip_address": external_ip_address, | 520 | "ip_address": external_ip_address, |
1931 | 434 | }, | 521 | }, |
1932 | 435 | ], | 522 | ], |
1933 | @@ -442,7 +529,7 @@ | |||
1934 | 442 | Equals([mac_external])) | 529 | Equals([mac_external])) |
1935 | 443 | self.expectThat( | 530 | self.expectThat( |
1936 | 444 | created_device["ip_assignment"], | 531 | created_device["ip_assignment"], |
1938 | 445 | Equals(DEVICE_IP_ASSIGNMENT.STATIC)) | 532 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.STATIC)) |
1939 | 446 | self.expectThat( | 533 | self.expectThat( |
1940 | 447 | created_device["ip_address"], Equals(static_ip_address)) | 534 | created_device["ip_address"], Equals(static_ip_address)) |
1941 | 448 | static_interface = Interface.objects.get(mac_address=MAC(mac_static)) | 535 | static_interface = Interface.objects.get(mac_address=MAC(mac_static)) |
1942 | @@ -467,7 +554,7 @@ | |||
1943 | 467 | "primary_mac": mac.lower(), # Lowercase. | 554 | "primary_mac": mac.lower(), # Lowercase. |
1944 | 468 | "interfaces": [{ | 555 | "interfaces": [{ |
1945 | 469 | "mac": mac.upper(), # Uppercase. | 556 | "mac": mac.upper(), # Uppercase. |
1947 | 470 | "ip_assignment": DEVICE_IP_ASSIGNMENT.DYNAMIC, | 557 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC, |
1948 | 471 | }], | 558 | }], |
1949 | 472 | }) | 559 | }) |
1950 | 473 | self.assertThat(created_device["primary_mac"], Equals(mac)) | 560 | self.assertThat(created_device["primary_mac"], Equals(mac)) |
1951 | @@ -482,7 +569,7 @@ | |||
1952 | 482 | "primary_mac": mac, # Colons. | 569 | "primary_mac": mac, # Colons. |
1953 | 483 | "interfaces": [{ | 570 | "interfaces": [{ |
1954 | 484 | "mac": mac.replace(":", "-"), # Hyphens. | 571 | "mac": mac.replace(":", "-"), # Hyphens. |
1956 | 485 | "ip_assignment": DEVICE_IP_ASSIGNMENT.DYNAMIC, | 572 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC, |
1957 | 486 | }], | 573 | }], |
1958 | 487 | }) | 574 | }) |
1959 | 488 | self.assertThat(created_device["primary_mac"], Equals(mac)) | 575 | self.assertThat(created_device["primary_mac"], Equals(mac)) |
1960 | @@ -511,12 +598,12 @@ | |||
1961 | 511 | updated_device = handler.create_interface({ | 598 | updated_device = handler.create_interface({ |
1962 | 512 | "system_id": device.system_id, | 599 | "system_id": device.system_id, |
1963 | 513 | "mac_address": mac, | 600 | "mac_address": mac, |
1965 | 514 | "ip_assignment": DEVICE_IP_ASSIGNMENT.DYNAMIC, | 601 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC, |
1966 | 515 | }) | 602 | }) |
1967 | 516 | self.expectThat(updated_device["primary_mac"], Equals(mac)) | 603 | self.expectThat(updated_device["primary_mac"], Equals(mac)) |
1968 | 517 | self.expectThat( | 604 | self.expectThat( |
1969 | 518 | updated_device["ip_assignment"], | 605 | updated_device["ip_assignment"], |
1971 | 519 | Equals(DEVICE_IP_ASSIGNMENT.DYNAMIC)) | 606 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC)) |
1972 | 520 | 607 | ||
1973 | 521 | @transactional | 608 | @transactional |
1974 | 522 | def test_create_interface_creates_with_external_ip_assignment(self): | 609 | def test_create_interface_creates_with_external_ip_assignment(self): |
1975 | @@ -528,13 +615,13 @@ | |||
1976 | 528 | updated_device = handler.create_interface({ | 615 | updated_device = handler.create_interface({ |
1977 | 529 | "system_id": device.system_id, | 616 | "system_id": device.system_id, |
1978 | 530 | "mac_address": mac, | 617 | "mac_address": mac, |
1980 | 531 | "ip_assignment": DEVICE_IP_ASSIGNMENT.EXTERNAL, | 618 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL, |
1981 | 532 | "ip_address": ip_address, | 619 | "ip_address": ip_address, |
1982 | 533 | }) | 620 | }) |
1983 | 534 | self.expectThat(updated_device["primary_mac"], Equals(mac)) | 621 | self.expectThat(updated_device["primary_mac"], Equals(mac)) |
1984 | 535 | self.expectThat( | 622 | self.expectThat( |
1985 | 536 | updated_device["ip_assignment"], | 623 | updated_device["ip_assignment"], |
1987 | 537 | Equals(DEVICE_IP_ASSIGNMENT.EXTERNAL)) | 624 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL)) |
1988 | 538 | self.expectThat( | 625 | self.expectThat( |
1989 | 539 | StaticIPAddress.objects.filter(ip=ip_address).count(), | 626 | StaticIPAddress.objects.filter(ip=ip_address).count(), |
1990 | 540 | Equals(1), "StaticIPAddress was not created.") | 627 | Equals(1), "StaticIPAddress was not created.") |
1991 | @@ -549,13 +636,13 @@ | |||
1992 | 549 | updated_device = handler.create_interface({ | 636 | updated_device = handler.create_interface({ |
1993 | 550 | "system_id": device.system_id, | 637 | "system_id": device.system_id, |
1994 | 551 | "mac_address": mac, | 638 | "mac_address": mac, |
1996 | 552 | "ip_assignment": DEVICE_IP_ASSIGNMENT.STATIC, | 639 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.STATIC, |
1997 | 553 | "subnet": subnet.id, | 640 | "subnet": subnet.id, |
1998 | 554 | }) | 641 | }) |
1999 | 555 | self.expectThat(updated_device["primary_mac"], Equals(mac)) | 642 | self.expectThat(updated_device["primary_mac"], Equals(mac)) |
2000 | 556 | self.expectThat( | 643 | self.expectThat( |
2001 | 557 | updated_device["ip_assignment"], | 644 | updated_device["ip_assignment"], |
2003 | 558 | Equals(DEVICE_IP_ASSIGNMENT.STATIC)) | 645 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.STATIC)) |
2004 | 559 | static_interface = Interface.objects.get(mac_address=MAC(mac)) | 646 | static_interface = Interface.objects.get(mac_address=MAC(mac)) |
2005 | 560 | observed_subnet = static_interface.ip_addresses.first().subnet | 647 | observed_subnet = static_interface.ip_addresses.first().subnet |
2006 | 561 | self.expectThat( | 648 | self.expectThat( |
2007 | @@ -573,14 +660,14 @@ | |||
2008 | 573 | updated_device = handler.create_interface({ | 660 | updated_device = handler.create_interface({ |
2009 | 574 | "system_id": device.system_id, | 661 | "system_id": device.system_id, |
2010 | 575 | "mac_address": mac, | 662 | "mac_address": mac, |
2012 | 576 | "ip_assignment": DEVICE_IP_ASSIGNMENT.STATIC, | 663 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.STATIC, |
2013 | 577 | "subnet": subnet.id, | 664 | "subnet": subnet.id, |
2014 | 578 | "ip_address": ip_address, | 665 | "ip_address": ip_address, |
2015 | 579 | }) | 666 | }) |
2016 | 580 | self.expectThat(updated_device["primary_mac"], Equals(mac)) | 667 | self.expectThat(updated_device["primary_mac"], Equals(mac)) |
2017 | 581 | self.expectThat( | 668 | self.expectThat( |
2018 | 582 | updated_device["ip_assignment"], | 669 | updated_device["ip_assignment"], |
2020 | 583 | Equals(DEVICE_IP_ASSIGNMENT.STATIC)) | 670 | Equals(DEVICE_IP_ASSIGNMENT_TYPE.STATIC)) |
2021 | 584 | self.expectThat(updated_device["ip_address"], Equals(ip_address)) | 671 | self.expectThat(updated_device["ip_address"], Equals(ip_address)) |
2022 | 585 | static_interface = Interface.objects.get(mac_address=MAC(mac)) | 672 | static_interface = Interface.objects.get(mac_address=MAC(mac)) |
2023 | 586 | observed_subnet = static_interface.ip_addresses.first().subnet | 673 | observed_subnet = static_interface.ip_addresses.first().subnet |
2024 | @@ -641,3 +728,238 @@ | |||
2025 | 641 | }}) | 728 | }}) |
2026 | 642 | device = reload_object(device) | 729 | device = reload_object(device) |
2027 | 643 | self.expectThat(device.zone, Equals(zone)) | 730 | self.expectThat(device.zone, Equals(zone)) |
2028 | 731 | |||
2029 | 732 | @transactional | ||
2030 | 733 | def test_create_interface_creates_interface(self): | ||
2031 | 734 | user = factory.make_admin() | ||
2032 | 735 | node = factory.make_Node(interface=False, node_type=NODE_TYPE.DEVICE) | ||
2033 | 736 | handler = DeviceHandler(user, {}) | ||
2034 | 737 | name = factory.make_name("eth") | ||
2035 | 738 | mac_address = factory.make_mac_address() | ||
2036 | 739 | handler.create_interface({ | ||
2037 | 740 | "system_id": node.system_id, | ||
2038 | 741 | "name": name, | ||
2039 | 742 | "mac_address": mac_address, | ||
2040 | 743 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC, | ||
2041 | 744 | }) | ||
2042 | 745 | self.assertEqual( | ||
2043 | 746 | 1, node.interface_set.count(), | ||
2044 | 747 | "Should have one interface on the node.") | ||
2045 | 748 | |||
2046 | 749 | @transactional | ||
2047 | 750 | def test_create_interface_creates_static(self): | ||
2048 | 751 | user = factory.make_admin() | ||
2049 | 752 | node = factory.make_Node(interface=False, node_type=NODE_TYPE.DEVICE) | ||
2050 | 753 | handler = DeviceHandler(user, {}) | ||
2051 | 754 | name = factory.make_name("eth") | ||
2052 | 755 | mac_address = factory.make_mac_address() | ||
2053 | 756 | fabric = factory.make_Fabric() | ||
2054 | 757 | vlan = fabric.get_default_vlan() | ||
2055 | 758 | subnet = factory.make_Subnet(vlan=vlan) | ||
2056 | 759 | handler.create_interface({ | ||
2057 | 760 | "system_id": node.system_id, | ||
2058 | 761 | "name": name, | ||
2059 | 762 | "mac_address": mac_address, | ||
2060 | 763 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.STATIC, | ||
2061 | 764 | "subnet": subnet.id, | ||
2062 | 765 | }) | ||
2063 | 766 | new_interface = node.interface_set.first() | ||
2064 | 767 | self.assertIsNotNone(new_interface) | ||
2065 | 768 | auto_ip = new_interface.ip_addresses.filter( | ||
2066 | 769 | alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet) | ||
2067 | 770 | self.assertIsNotNone(auto_ip) | ||
2068 | 771 | self.assertEqual(1, len(auto_ip)) | ||
2069 | 772 | |||
2070 | 773 | @transactional | ||
2071 | 774 | def test_create_interface_creates_external(self): | ||
2072 | 775 | user = factory.make_admin() | ||
2073 | 776 | node = factory.make_Node(interface=False, node_type=NODE_TYPE.DEVICE) | ||
2074 | 777 | handler = DeviceHandler(user, {}) | ||
2075 | 778 | name = factory.make_name("eth") | ||
2076 | 779 | mac_address = factory.make_mac_address() | ||
2077 | 780 | ip_address = factory.make_ip_address() | ||
2078 | 781 | handler.create_interface({ | ||
2079 | 782 | "system_id": node.system_id, | ||
2080 | 783 | "name": name, | ||
2081 | 784 | "mac_address": mac_address, | ||
2082 | 785 | "ip_assignment": DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL, | ||
2083 | 786 | "ip_address": ip_address, | ||
2084 | 787 | }) | ||
2085 | 788 | new_interface = node.interface_set.first() | ||
2086 | 789 | self.assertIsNotNone(new_interface) | ||
2087 | 790 | auto_ip = new_interface.ip_addresses.filter( | ||
2088 | 791 | alloc_type=IPADDRESS_TYPE.USER_RESERVED) | ||
2089 | 792 | self.assertIsNotNone(auto_ip) | ||
2090 | 793 | self.assertEqual(1, len(auto_ip)) | ||
2091 | 794 | |||
2092 | 795 | @transactional | ||
2093 | 796 | def test_update_interface_updates(self): | ||
2094 | 797 | user = factory.make_admin() | ||
2095 | 798 | node = factory.make_Node(interface=False, node_type=NODE_TYPE.DEVICE) | ||
2096 | 799 | handler = DeviceHandler(user, {}) | ||
2097 | 800 | name = factory.make_name("eth") | ||
2098 | 801 | mac_address = factory.make_mac_address() | ||
2099 | 802 | ip_assignment = random.choice(DEVICE_IP_ASSIGNMENT_TYPE_CHOICES)[0] | ||
2100 | 803 | params = { | ||
2101 | 804 | "system_id": node.system_id, | ||
2102 | 805 | "name": name, | ||
2103 | 806 | "mac_address": mac_address, | ||
2104 | 807 | "ip_assignment": ip_assignment, | ||
2105 | 808 | } | ||
2106 | 809 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
2107 | 810 | subnet = factory.make_Subnet() | ||
2108 | 811 | params['subnet'] = subnet.id | ||
2109 | 812 | ip_address = str(IPAddress(IPNetwork(subnet.cidr).first)) | ||
2110 | 813 | params['ip_address'] = ip_address | ||
2111 | 814 | elif ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL: | ||
2112 | 815 | ip_address = factory.make_ip_address() | ||
2113 | 816 | params['ip_address'] = ip_address | ||
2114 | 817 | handler.create_interface(params) | ||
2115 | 818 | interface = node.interface_set.first() | ||
2116 | 819 | self.assertIsNotNone(interface) | ||
2117 | 820 | new_name = factory.make_name("eth") | ||
2118 | 821 | new_ip_assignment = random.choice(DEVICE_IP_ASSIGNMENT_TYPE_CHOICES)[0] | ||
2119 | 822 | new_params = { | ||
2120 | 823 | "system_id": node.system_id, | ||
2121 | 824 | "interface_id": interface.id, | ||
2122 | 825 | "name": new_name, | ||
2123 | 826 | "mac_address": mac_address, | ||
2124 | 827 | "ip_assignment": new_ip_assignment, | ||
2125 | 828 | } | ||
2126 | 829 | if new_ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
2127 | 830 | new_subnet = factory.make_Subnet() | ||
2128 | 831 | new_params['subnet'] = new_subnet.id | ||
2129 | 832 | new_ip_address = str(IPAddress(IPNetwork(new_subnet.cidr).first)) | ||
2130 | 833 | new_params['ip_address'] = new_ip_address | ||
2131 | 834 | elif new_ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.EXTERNAL: | ||
2132 | 835 | new_ip_address = factory.make_ip_address() | ||
2133 | 836 | new_params['ip_address'] = new_ip_address | ||
2134 | 837 | handler.update_interface(new_params) | ||
2135 | 838 | data = self.dehydrate_device(node, user)['interfaces'] | ||
2136 | 839 | self.assertEqual(1, len(data)) | ||
2137 | 840 | self.assertEqual(data[0]['ip_assignment'], new_ip_assignment) | ||
2138 | 841 | if new_ip_assignment != DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: | ||
2139 | 842 | self.assertEqual(data[0]['ip_address'], new_ip_address) | ||
2140 | 843 | |||
2141 | 844 | @transactional | ||
2142 | 845 | def test_update_raise_permissions_error_for_non_admin(self): | ||
2143 | 846 | user = factory.make_User() | ||
2144 | 847 | handler = DeviceHandler(user, {}) | ||
2145 | 848 | self.assertRaises( | ||
2146 | 849 | HandlerPermissionError, | ||
2147 | 850 | handler.update, {}) | ||
2148 | 851 | |||
2149 | 852 | @transactional | ||
2150 | 853 | def test_update_does_not_raise_validation_error_for_invalid_arch(self): | ||
2151 | 854 | user = factory.make_admin() | ||
2152 | 855 | handler = DeviceHandler(user, {}) | ||
2153 | 856 | node = factory.make_Node(interface=True, node_type=NODE_TYPE.DEVICE) | ||
2154 | 857 | node_data = self.dehydrate_device(node, user) | ||
2155 | 858 | arch = factory.make_name("arch") | ||
2156 | 859 | node_data["architecture"] = arch | ||
2157 | 860 | handler.update(node_data) | ||
2158 | 861 | # succeeds, because Devices don't care about architecture. | ||
2159 | 862 | |||
2160 | 863 | @transactional | ||
2161 | 864 | def test_update_updates_node(self): | ||
2162 | 865 | user = factory.make_admin() | ||
2163 | 866 | handler = DeviceHandler(user, {}) | ||
2164 | 867 | node = factory.make_Node(interface=True, node_type=NODE_TYPE.DEVICE) | ||
2165 | 868 | node_data = self.dehydrate_device(node, user) | ||
2166 | 869 | new_zone = factory.make_Zone() | ||
2167 | 870 | new_hostname = factory.make_name("hostname") | ||
2168 | 871 | node_data["hostname"] = new_hostname | ||
2169 | 872 | node_data["zone"] = { | ||
2170 | 873 | "name": new_zone.name, | ||
2171 | 874 | } | ||
2172 | 875 | updated_node = handler.update(node_data) | ||
2173 | 876 | self.assertEqual(updated_node["hostname"], new_hostname) | ||
2174 | 877 | self.assertEqual(updated_node["zone"]["id"], new_zone.id) | ||
2175 | 878 | |||
2176 | 879 | @transactional | ||
2177 | 880 | def test_delete_interface(self): | ||
2178 | 881 | user = factory.make_admin() | ||
2179 | 882 | node = factory.make_Node(node_type=NODE_TYPE.DEVICE) | ||
2180 | 883 | handler = DeviceHandler(user, {}) | ||
2181 | 884 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | ||
2182 | 885 | handler.delete_interface({ | ||
2183 | 886 | "system_id": node.system_id, | ||
2184 | 887 | "interface_id": interface.id, | ||
2185 | 888 | }) | ||
2186 | 889 | self.assertIsNone(reload_object(interface)) | ||
2187 | 890 | |||
2188 | 891 | @transactional | ||
2189 | 892 | def test_link_subnet_calls_update_link_by_id_if_link_id(self): | ||
2190 | 893 | user = factory.make_admin() | ||
2191 | 894 | node = factory.make_Node(node_type=NODE_TYPE.DEVICE) | ||
2192 | 895 | handler = DeviceHandler(user, {}) | ||
2193 | 896 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | ||
2194 | 897 | subnet = factory.make_Subnet() | ||
2195 | 898 | link_id = random.randint(0, 100) | ||
2196 | 899 | ip_assignment = factory.pick_enum(DEVICE_IP_ASSIGNMENT_TYPE) | ||
2197 | 900 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
2198 | 901 | mode = INTERFACE_LINK_TYPE.STATIC | ||
2199 | 902 | elif ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: | ||
2200 | 903 | mode = INTERFACE_LINK_TYPE.DHCP | ||
2201 | 904 | else: | ||
2202 | 905 | mode = INTERFACE_LINK_TYPE.LINK_UP | ||
2203 | 906 | ip_address = factory.make_ip_address() | ||
2204 | 907 | self.patch_autospec(Interface, "update_link_by_id") | ||
2205 | 908 | handler.link_subnet({ | ||
2206 | 909 | "system_id": node.system_id, | ||
2207 | 910 | "interface_id": interface.id, | ||
2208 | 911 | "link_id": link_id, | ||
2209 | 912 | "subnet": subnet.id, | ||
2210 | 913 | "ip_assignment": ip_assignment, | ||
2211 | 914 | "ip_address": ip_address, | ||
2212 | 915 | }) | ||
2213 | 916 | self.assertThat( | ||
2214 | 917 | Interface.update_link_by_id, | ||
2215 | 918 | MockCalledOnceWith( | ||
2216 | 919 | ANY, link_id, mode, subnet, ip_address=ip_address)) | ||
2217 | 920 | |||
2218 | 921 | @transactional | ||
2219 | 922 | def test_link_subnet_calls_link_subnet_if_not_link_id(self): | ||
2220 | 923 | user = factory.make_admin() | ||
2221 | 924 | node = factory.make_Node(node_type=NODE_TYPE.DEVICE) | ||
2222 | 925 | handler = DeviceHandler(user, {}) | ||
2223 | 926 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | ||
2224 | 927 | subnet = factory.make_Subnet() | ||
2225 | 928 | ip_assignment = factory.pick_enum(DEVICE_IP_ASSIGNMENT_TYPE) | ||
2226 | 929 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
2227 | 930 | mode = INTERFACE_LINK_TYPE.STATIC | ||
2228 | 931 | elif ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.DYNAMIC: | ||
2229 | 932 | mode = INTERFACE_LINK_TYPE.DHCP | ||
2230 | 933 | else: | ||
2231 | 934 | mode = INTERFACE_LINK_TYPE.LINK_UP | ||
2232 | 935 | ip_address = factory.make_ip_address() | ||
2233 | 936 | self.patch_autospec(Interface, "link_subnet") | ||
2234 | 937 | handler.link_subnet({ | ||
2235 | 938 | "system_id": node.system_id, | ||
2236 | 939 | "interface_id": interface.id, | ||
2237 | 940 | "subnet": subnet.id, | ||
2238 | 941 | "ip_assignment": ip_assignment, | ||
2239 | 942 | "ip_address": ip_address, | ||
2240 | 943 | }) | ||
2241 | 944 | if ip_assignment == DEVICE_IP_ASSIGNMENT_TYPE.STATIC: | ||
2242 | 945 | self.assertThat( | ||
2243 | 946 | Interface.link_subnet, | ||
2244 | 947 | MockCalledOnceWith( | ||
2245 | 948 | ANY, mode, subnet, ip_address=ip_address)) | ||
2246 | 949 | else: | ||
2247 | 950 | self.assertThat(Interface.link_subnet, MockNotCalled()) | ||
2248 | 951 | |||
2249 | 952 | @transactional | ||
2250 | 953 | def test_unlink_subnet(self): | ||
2251 | 954 | user = factory.make_admin() | ||
2252 | 955 | node = factory.make_Node(node_type=NODE_TYPE.DEVICE) | ||
2253 | 956 | handler = DeviceHandler(user, {}) | ||
2254 | 957 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) | ||
2255 | 958 | link_ip = factory.make_StaticIPAddress( | ||
2256 | 959 | alloc_type=IPADDRESS_TYPE.AUTO, ip="", interface=interface) | ||
2257 | 960 | handler.delete_interface({ | ||
2258 | 961 | "system_id": node.system_id, | ||
2259 | 962 | "interface_id": interface.id, | ||
2260 | 963 | "link_id": link_ip.id, | ||
2261 | 964 | }) | ||
2262 | 965 | self.assertIsNone(reload_object(link_ip)) |
Just a few comments from quickly going over this code.
You were worried about query counts going up. That's a good thing to have noticed and to worry about, but it's nothing unusual: it's the curse of ORMs. The dehydrate_* method pattern is nice to work with but it exacerbates this problem.
The first tool I'd reach for is tests, specifically using CountQueries / count_queries. Typically I'd do something like "render n things" and then "render n+1 things" and assert that the query count is the same for both. Then I'd add pre-fetching in the "right" places until the test passes.
Overall, there's a lot of stuff in here. It's already at ~1000 lines of diff, but it's densely populated. Splitting it into 2 or more pieces will help a lot at review time.