Merge lp:~ltrager/maas/delete_rack into lp:~maas-committers/maas/trunk

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: no longer in the source branch.
Merged at revision: 5002
Proposed branch: lp:~ltrager/maas/delete_rack
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 130 lines (+38/-13)
6 files modified
src/maasserver/models/node.py (+13/-1)
src/maasserver/models/tests/test_node.py (+14/-0)
src/maasserver/models/tests/test_vlan.py (+4/-0)
src/maasserver/models/vlan.py (+3/-1)
src/maasserver/static/partials/node-details.html (+1/-9)
src/maasserver/websockets/handlers/node.py (+3/-2)
To merge this branch: bzr merge lp:~ltrager/maas/delete_rack
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Andres Rodriguez (community) Approve
Jeffrey C Jones (community) Approve
Review via email: mp+292330@code.launchpad.net

Commit message

Allow deleting a rack controller from the UI and provide more descriptive error messages when unable to delete.

Description of the change

This add the ability to delete rack controllers in the UI. I added more descriptive error messages which are shown in both the UI and API when unable to delete a rack controller due to being attached to a VLAN. If a rack controller is unable to be deleted due to it being set as a secondary rack controller the UI will ask the user if they want to unlink the rack controller from all VLANs and try to delete again.

Deleting a rack controller still deletes the node object. Let me know if we want to have it convert back to a machine.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Not a full review, just on the messaging.

review: Needs Information
Revision history for this message
Lee Trager (ltrager) wrote :

I've updated the error messages to display the <fabric name>.<vlan name> when displaying which VLAN's a rack is still connected to. I noticed that VLAN.get_name didn't handle unnamed VLAN's so it now returns the VID if the VLAN has no name.

These messages appear over the API and UI when trying to delete a rack controller which are still set to one or more VLAN's primary or secondary rack controller. If the rack controller is unable to be deleted due to being a secondary rack controller on one or more VLAN's the UI will ask the user if they'd like to remove that association and then delete the rack.

Revision history for this message
Jeffrey C Jones (trapnine) wrote :

Looks awesome, just a test needed for the error message string matching (and some comments about it), and some simple typos.

review: Needs Fixing
Revision history for this message
Lee Trager (ltrager) wrote :

Thanks for the review. I've fixed the typos and added the missing tests.

Revision history for this message
Jeffrey C Jones (trapnine) wrote :

> Thanks for the review. I've fixed the typos and added the missing tests.

Lookin' good.

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Sorry this took so long for me to review.

Some comments below, mostly non-blocking.

But I was a bit confused about the "force" behavior, so I cloned your branch locally to test it out. I configured DHCP on the VLAN and got an error that the rack was still connected. So I stopped the rack, then got the error that it was a primary DHCP provider on a VLAN. So I disabled DHCP and tried it again; then I got an error as follows:

The delete action for 1 controller failed with error: 'RackController' object has no attribute '_fields_last_seen_values__node_type'

Full traceback here:

https://paste.ubuntu.com/16089729/

So that needs to be fixed before this can land.

Just curious why the "force" parameter was added? It looks like there is no way for a user to pass in force=True; at least I didn't see it in the UI?

review: Needs Fixing
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Ok, Mike's review highlights something. If I understand correctly:

"got an error that the rack was still connected."

That means that if a rack controller is running we cannot delete the rack? That is a serious issue that we need to fix (a usability one). We should not, at all cost, have to stop maas-rackd manually before we can delete a rack.

In fact, I'm thinking that we have to automatically tell the rack to stop, however, the problem is that if we are to restart the machine, it will auto-register again. As such, we need to discuss this specific problem.

review: Needs Fixing
Revision history for this message
Lee Trager (ltrager) wrote :

Deleting a rack now always automatically removes any secondary_rack links. The only change to the UI is that the operations drop down is displayed on the node-list page. As per our discussion this morning the rack still has to be manually shutoff. I'll post a review later which disables the maas-rackd service.

Mike, I took a look at str(VLAN), its outputting "%s.%s" % (self.fabric.get_name(), self.get_name()). Is that close enough to what you wanted displayed or did you want it to be "%s on %s" % (vlan.get_name(), vlan.fabric.get_name())? Should I change the __str__ method?

Revision history for this message
Andres Rodriguez (andreserl) wrote :

So I just want to confirm. When you delete a rack controller that's a secondary rack controlller, it will show aconfirmation message to proceed, otherwise cancel right?

Revision history for this message
Lee Trager (ltrager) wrote :

The Cancel/Go message is displayed in the UI when you click delete the same way its displayed when deleting a machine or device. If the rack controller is a secondary rack controller the association will be automatically removed once you click Go.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

I'm ok with this branch now, although, I'd like mike to review it before landing.

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good to me. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/models/node.py'
2--- src/maasserver/models/node.py 2016-05-09 17:19:23 +0000
3+++ src/maasserver/models/node.py 2016-05-09 19:08:38 +0000
4@@ -3644,11 +3644,23 @@
5 from maasserver.models import RegionRackRPCConnection
6 connections = RegionRackRPCConnection.objects.filter(
7 rack_controller=self)
8-
9 if len(connections) != 0:
10 raise ValidationError(
11 "Unable to delete %s as it's currently connected to one or "
12 "more regions." % self.hostname)
13+
14+ primary_vlans = VLAN.objects.filter(primary_rack=self)
15+ if len(primary_vlans) != 0:
16+ raise ValidationError(
17+ "Unable to delete '%s'; it is currently set as a primary rack"
18+ " controller on VLANs %s" %
19+ (self.hostname,
20+ ', '.join([str(vlan) for vlan in primary_vlans])))
21+
22+ for vlan in VLAN.objects.filter(secondary_rack=self):
23+ vlan.secondary_rack = None
24+ vlan.save()
25+
26 if self.node_type == NODE_TYPE.REGION_AND_RACK_CONTROLLER:
27 self.node_type = NODE_TYPE.REGION_CONTROLLER
28 self.save()
29
30=== modified file 'src/maasserver/models/tests/test_node.py'
31--- src/maasserver/models/tests/test_node.py 2016-05-04 16:23:11 +0000
32+++ src/maasserver/models/tests/test_node.py 2016-05-09 19:08:38 +0000
33@@ -6951,6 +6951,20 @@
34 mock_filter.return_value = [rackcontroller]
35 self.assertRaises(ValidationError, rackcontroller.delete)
36
37+ def test_prevents_delete_when_primary_rack(self):
38+ rackcontroller = factory.make_RackController()
39+ factory.make_VLAN(primary_rack=rackcontroller)
40+ self.assertRaises(ValidationError, rackcontroller.delete)
41+
42+ def test_delete_removes_secondary_link(self):
43+ rackcontroller = factory.make_RackController()
44+ vlan = factory.make_VLAN(secondary_rack=rackcontroller)
45+ rackcontroller.delete()
46+ self.assertIsNone(reload_object(vlan).secondary_rack)
47+ self.assertRaises(
48+ RackController.DoesNotExist,
49+ RackController.objects.get, system_id=rackcontroller.system_id)
50+
51 def test_delete_converts_region_and_rack_to_region(self):
52 region_and_rack = factory.make_Node(
53 node_type=NODE_TYPE.REGION_AND_RACK_CONTROLLER)
54
55=== modified file 'src/maasserver/models/tests/test_vlan.py'
56--- src/maasserver/models/tests/test_vlan.py 2016-03-28 13:54:47 +0000
57+++ src/maasserver/models/tests/test_vlan.py 2016-05-09 19:08:38 +0000
58@@ -102,6 +102,10 @@
59 vlan = factory.make_VLAN(name=name)
60 self.assertEqual(name, vlan.get_name())
61
62+ def test_get_name_for_unnamed_vlan(self):
63+ vlan = factory.make_VLAN()
64+ self.assertEqual(str(vlan.vid), vlan.get_name())
65+
66 def test_creates_vlan(self):
67 name = factory.make_name('name')
68 vid = random.randint(3, 55)
69
70=== modified file 'src/maasserver/models/vlan.py'
71--- src/maasserver/models/vlan.py 2016-04-27 20:38:06 +0000
72+++ src/maasserver/models/vlan.py 2016-05-09 19:08:38 +0000
73@@ -246,8 +246,10 @@
74 """Return the name of the VLAN."""
75 if self.is_fabric_default():
76 return "untagged"
77- else:
78+ elif self.name is not None:
79 return self.name
80+ else:
81+ return str(self.vid)
82
83 def manage_connected_interfaces(self):
84 """Deal with connected interfaces:
85
86=== modified file 'src/maasserver/static/partials/node-details.html'
87--- src/maasserver/static/partials/node-details.html 2016-04-26 19:52:34 +0000
88+++ src/maasserver/static/partials/node-details.html 2016-05-09 19:08:38 +0000
89@@ -39,16 +39,8 @@
90 data-ng-disabled="editHeaderInvalid()"
91 data-ng-click="saveEditHeader()">Save</a>
92 </h1>
93- <!-- XXX trapnine 2016-02-21 - Sample HTML disabled until implemented. -->
94- <div class="page-header__actions four-col last-col" data-ng-show="false && isController">
95- <div class="page-header__cta two-col no-margin-bottom last-col">
96- <div class="two-col no-margin-bottom last-col">
97- <a href="#" class="link-cta-ubuntu right">Remove</a>
98- </div>
99- </div>
100- </div>
101 <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
102- <div data-ng-show="!isController && !header.editing && availableActionOptions.length">
103+ <div data-ng-show="!header.editing && availableActionOptions.length">
104 <div class="page-header__actions four-col last-col">
105 <div class="page-header__cta two-col no-margin-bottom last-col">
106 <div class="two-col no-margin-bottom last-col">
107
108=== modified file 'src/maasserver/websockets/handlers/node.py'
109--- src/maasserver/websockets/handlers/node.py 2016-03-28 13:54:47 +0000
110+++ src/maasserver/websockets/handlers/node.py 2016-05-09 19:08:38 +0000
111@@ -21,6 +21,7 @@
112 from maasserver.models.config import Config
113 from maasserver.models.event import Event
114 from maasserver.models.filesystemgroup import VolumeGroup
115+from maasserver.models.node import typecast_to_node_type
116 from maasserver.models.nodeprobeddetails import get_single_probed_details
117 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
118 from maasserver.models.virtualblockdevice import VirtualBlockDevice
119@@ -476,9 +477,9 @@
120 """Get object by using the `pk` in `params`."""
121 obj = super(NodeHandler, self).get_object(params)
122 if self.user.is_superuser:
123- return obj
124+ return typecast_to_node_type(obj)
125 if obj.owner is None or obj.owner == self.user:
126- return obj
127+ return typecast_to_node_type(obj)
128 raise HandlerDoesNotExistError(params[self._meta.pk])
129
130 def get_mac_addresses(self, data):