launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #12112
[Merge] lp:~rvb/maas/nodegroup-api-methods-2 into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/nodegroup-api-methods-2 into lp:maas with lp:~rvb/maas/nodegroup-api-methods as a prerequisite.
Requested reviews:
MAAS Maintainers (maas-maintainers)
For more details, see:
https://code.launchpad.net/~rvb/maas/nodegroup-api-methods-2/+merge/124854
This branch add the API methods to manage the interfaces attached to a nodegroup.
= Pre-imp =
This was discussed with Gavin
= Notes =
The original idea was to omit the 'management' parameter from the arguments used by the method 'new' and 'update' and add a 'change_management' method… but, since we're going to fix bug 1052339 eventually, I thought it would be more logical to create the right API methods right from the start and deal with the fact that we can only have one "managed" interface per nodegroup right now in the form validation. This mean I had to add validation code that is not that pretty but the good news is that this code will go away when we will fix bug 1052339.
--
https://code.launchpad.net/~rvb/maas/nodegroup-api-methods-2/+merge/124854
Your team MAAS Maintainers is requested to review the proposed merge of lp:~rvb/maas/nodegroup-api-methods-2 into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-09-18 07:59:20 +0000
+++ src/maasserver/api.py 2012-09-18 07:59:21 +0000
@@ -63,6 +63,8 @@
"FilesHandler",
"get_oauth_token",
"NodeGroupsHandler",
+ "NodeGroupInterfaceHandler",
+ "NodeGroupInterfacesHandler",
"NodeHandler",
"NodeMacHandler",
"NodeMacsHandler",
@@ -129,6 +131,7 @@
from maasserver.forms import (
get_node_create_form,
get_node_edit_form,
+ NodeGroupInterfaceForm,
NodeGroupWithInterfacesForm,
)
from maasserver.models import (
@@ -139,6 +142,7 @@
MACAddress,
Node,
NodeGroup,
+ NodeGroupInterface,
)
from maasserver.preseed import (
compose_enlistment_preseed_url,
@@ -872,16 +876,19 @@
return ('files_handler', [])
+DISPLAYED_NODEGROUP_FIELDS = ('uuid', 'status', 'name')
+
+
@api_operations
class AnonNodeGroupsHandler(AnonymousBaseHandler):
"""Anon Node-groups API."""
-
allowed_methods = ('GET', 'POST')
+ fields = DISPLAYED_NODEGROUP_FIELDS
- def read(self, request):
- """Index of node groups."""
- return HttpResponse(sorted(
- [nodegroup.uuid for nodegroup in NodeGroup.objects.all()]))
+ @api_exported('GET')
+ def list(self, request):
+ """List of node groups."""
+ return NodeGroup.objects.all()
@classmethod
def resource_uri(cls):
@@ -962,6 +969,12 @@
"""Node-groups API."""
anonymous = AnonNodeGroupsHandler
allowed_methods = ('GET', 'POST')
+ fields = DISPLAYED_NODEGROUP_FIELDS
+
+ @api_exported('GET')
+ def list(self, request):
+ """List of node groups."""
+ return NodeGroup.objects.all()
@api_exported('POST')
def accept(self, request):
@@ -1026,7 +1039,7 @@
"""Node-group API."""
allowed_methods = ('GET', 'POST')
- fields = ('name', 'uuid')
+ fields = DISPLAYED_NODEGROUP_FIELDS
def read(self, request, uuid):
"""GET a node group."""
@@ -1053,6 +1066,117 @@
return HttpResponse("Leases updated.", status=httplib.OK)
+DISPLAYED_NODEGROUP_FIELDS = (
+ 'ip', 'management', 'interface', 'subnet_mask',
+ 'broadcast_ip', 'ip_range_low', 'ip_range_high')
+
+
+@api_operations
+class NodeGroupInterfacesHandler(BaseHandler):
+ """NodeGroupInterfaces API."""
+ allowed_methods = ('GET', 'POST')
+ fields = DISPLAYED_NODEGROUP_FIELDS
+
+ @api_exported('GET')
+ def list(self, request, uuid):
+ """List of NodeGroupInterfaces of a NodeGroup."""
+ nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
+ return NodeGroupInterface.objects.filter(nodegroup=nodegroup)
+
+ @api_exported('POST')
+ def new(self, request, uuid):
+ """Create a new NodeGroupInterface for this NodeGroup.
+
+ :param ip: Static IP of the interface.
+ :type ip: basestring (IP Address)
+ :param interface: Name of the interface.
+ :type interface: basestring
+ :param management: The service(s) MAAS should manage on this interface.
+ :type management: Vocabulary `NODEGROUPINTERFACE_MANAGEMENT`
+ :param subnet_mask: Subnet mask, e.g. 255.0.0.0.
+ :type subnet_mask: basestring (IP Address)
+ :param broadcast_ip: Broadcast address for this subnet.
+ :type broadcast_ip: basestring (IP Address)
+ :param router_ip: Address of default gateway.
+ :type router_ip: basestring (IP Address)
+ :param ip_range_low: Lowest IP address to assign to clients.
+ :type ip_range_low: basestring (IP Address)
+ :param ip_range_high: Highest IP address to assign to clients.
+ :type ip_range_high: basestring (IP Address)
+ """
+ nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
+ form = NodeGroupInterfaceForm(request.data)
+ if form.is_valid():
+ return form.save(
+ nodegroup=nodegroup)
+ else:
+ raise ValidationError(form.errors)
+
+ @classmethod
+ def resource_uri(cls, nodegroup=None):
+ if nodegroup is None:
+ uuid = 'uuid'
+ else:
+ uuid = nodegroup.uuid
+ return ('nodegroupinterfaces_handler', [uuid])
+
+
+class NodeGroupInterfaceHandler(BaseHandler):
+ """NodeGroupInterface API."""
+ allowed_methods = ('GET', 'PUT')
+ fields = DISPLAYED_NODEGROUP_FIELDS
+
+ def read(self, request, uuid, interface):
+ """List of NodeGroupInterfaces of a NodeGroup."""
+ nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
+ nodegroupinterface = get_object_or_404(
+ NodeGroupInterface, nodegroup=nodegroup, interface=interface)
+ return nodegroupinterface
+
+ def update(self, request, uuid, interface):
+ """Update a specific NodeGroupInterface.
+
+ :param ip: Static IP of the interface.
+ :type ip: basestring (IP Address)
+ :param interface: Name of the interface.
+ :type interface: basestring
+ :param management: The service(s) MAAS should manage on this interface.
+ :type management: Vocabulary `NODEGROUPINTERFACE_MANAGEMENT`
+ :param subnet_mask: Subnet mask, e.g. 255.0.0.0.
+ :type subnet_mask: basestring (IP Address)
+ :param broadcast_ip: Broadcast address for this subnet.
+ :type broadcast_ip: basestring (IP Address)
+ :param router_ip: Address of default gateway.
+ :type router_ip: basestring (IP Address)
+ :param ip_range_low: Lowest IP address to assign to clients.
+ :type ip_range_low: basestring (IP Address)
+ :param ip_range_high: Highest IP address to assign to clients.
+ :type ip_range_high: basestring (IP Address)
+ """
+ nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
+ nodegroupinterface = get_object_or_404(
+ NodeGroupInterface, nodegroup=nodegroup, interface=interface)
+ data = get_overrided_query_dict(
+ model_to_dict(nodegroupinterface), request.data)
+ form = NodeGroupInterfaceForm(data, instance=nodegroupinterface)
+ if form.is_valid():
+ return form.save()
+ else:
+ raise ValidationError(form.errors)
+
+ @classmethod
+ def resource_uri(cls, nodegroup=None, interface=None):
+ if nodegroup is None:
+ uuid = 'uuid'
+ else:
+ uuid = nodegroup.uuid
+ if interface is None:
+ interface_name = 'interface'
+ else:
+ interface_name = interface.interface
+ return ('nodegroupinterface_handler', [uuid, interface_name])
+
+
@api_operations
class AccountHandler(BaseHandler):
"""Manage the current logged-in user."""
=== modified file 'src/maasserver/enum.py'
--- src/maasserver/enum.py 2012-09-12 15:37:13 +0000
+++ src/maasserver/enum.py 2012-09-18 07:59:21 +0000
@@ -167,7 +167,7 @@
class NODEGROUPINTERFACE_MANAGEMENT:
"""The vocabulary of a `NodeGroupInterface`'s possible statuses."""
# A nodegroupinterface starts out as UNMANAGED.
- DEFAULT_STATUS = 0
+ DEFAULT = 0
# Do not manage DHCP or DNS for this interface.
UNMANAGED = 0
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2012-09-14 07:15:49 +0000
+++ src/maasserver/forms.py 2012-09-18 07:59:21 +0000
@@ -60,6 +60,7 @@
NODE_AFTER_COMMISSIONING_ACTION_CHOICES,
NODEGROUP_STATUS,
NODEGROUPINTERFACE_MANAGEMENT,
+ NODEGROUPINTERFACE_MANAGEMENT_CHOICES,
)
from maasserver.fields import MACAddressFormField
from maasserver.models import (
@@ -610,6 +611,10 @@
class NodeGroupInterfaceForm(ModelForm):
+ management = forms.TypedChoiceField(
+ choices=NODEGROUPINTERFACE_MANAGEMENT_CHOICES, required=False,
+ coerce=int, empty_value=NODEGROUPINTERFACE_MANAGEMENT.DEFAULT)
+
class Meta:
model = NodeGroupInterface
fields = (
@@ -620,12 +625,32 @@
'router_ip',
'ip_range_low',
'ip_range_high',
+ 'management',
)
- def save(self, nodegroup, management, *args, **kwargs):
+ def clean_management(self):
+ # XXX: rvb 2012-09-18 bug=1052339: Only one "managed" interface
+ # is supported per NodeGroup.
+ management = self.cleaned_data['management']
+ if management != NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED:
+ nodegroup_interfaces = NodeGroupInterface.objects.all()
+ if self.instance and self.instance.id is not None:
+ nodegroup_interfaces = nodegroup_interfaces.exclude(
+ id=self.instance.id)
+ exist_other_managed_interface = (
+ nodegroup_interfaces.exclude(
+ management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED).exists())
+ if exist_other_managed_interface:
+ raise ValidationError(
+ {'management': [
+ "Another managed interface already exists for this "
+ "nodegroup."]})
+ return management
+
+ def save(self, nodegroup=None, *args, **kwargs):
interface = super(NodeGroupInterfaceForm, self).save(commit=False)
- interface.nodegroup = nodegroup
- interface.management = management
+ if nodegroup is not None:
+ interface.nodegroup = nodegroup
if kwargs.get('commit', True):
interface.save(*args, **kwargs)
return interface
@@ -658,6 +683,7 @@
except ValueError:
raise forms.ValidationError("Invalid json value.")
else:
+ managed = []
# Raise an exception if the interfaces json object is not a list.
if not isinstance(interfaces, collections.Iterable):
raise forms.ValidationError(
@@ -672,16 +698,23 @@
raise forms.ValidationError(
"Invalid interface: %r (%r)." % (
interface, form._errors))
+ management = interface.get('management', '')
+ if management not in (
+ '', NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED):
+ managed.append(management)
+ # XXX: rvb 2012-09-18 bug=1052339: Only one "managed" interface
+ # is supported per NodeGroup.
+ if len(managed) > 1:
+ raise ValidationError(
+ "Only one managed interface can be configured for this "
+ "nodegroup")
return interfaces
def save(self):
nodegroup = super(NodeGroupWithInterfacesForm, self).save()
for interface in self.cleaned_data['interfaces']:
- # Create an unmanaged interface.
form = NodeGroupInterfaceForm(data=interface)
- form.save(
- nodegroup=nodegroup,
- management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
+ form.save(nodegroup=nodegroup)
# Set the nodegroup to be 'PENDING'.
nodegroup.status = NODEGROUP_STATUS.PENDING
nodegroup.save()
=== modified file 'src/maasserver/models/nodegroup.py'
--- src/maasserver/models/nodegroup.py 2012-09-18 07:59:20 +0000
+++ src/maasserver/models/nodegroup.py 2012-09-18 07:59:21 +0000
@@ -55,7 +55,7 @@
def new(self, name, uuid, ip, subnet_mask=None,
broadcast_ip=None, router_ip=None, ip_range_low=None,
ip_range_high=None, dhcp_key='', interface='',
- management=NODEGROUPINTERFACE_MANAGEMENT.DEFAULT_STATUS):
+ management=NODEGROUPINTERFACE_MANAGEMENT.DEFAULT):
"""Create a :class:`NodeGroup` with the given parameters.
This method will:
=== modified file 'src/maasserver/models/nodegroupinterface.py'
--- src/maasserver/models/nodegroupinterface.py 2012-09-13 16:26:24 +0000
+++ src/maasserver/models/nodegroupinterface.py 2012-09-18 07:59:21 +0000
@@ -42,8 +42,8 @@
'maasserver.NodeGroup', editable=True, null=False, blank=False)
management = IntegerField(
- choices=NODEGROUPINTERFACE_MANAGEMENT_CHOICES, editable=False,
- default=NODEGROUPINTERFACE_MANAGEMENT.DEFAULT_STATUS)
+ choices=NODEGROUPINTERFACE_MANAGEMENT_CHOICES, editable=True,
+ default=NODEGROUPINTERFACE_MANAGEMENT.DEFAULT)
# DHCP server settings.
interface = CharField(
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-09-18 07:59:20 +0000
+++ src/maasserver/tests/test_api.py 2012-09-18 07:59:21 +0000
@@ -37,6 +37,7 @@
from fixtures import Fixture
from maasserver import api
from maasserver.api import (
+ DISPLAYED_NODEGROUP_FIELDS,
extract_constraints,
extract_oauth_key,
extract_oauth_key_from_auth_header,
@@ -51,6 +52,7 @@
NODE_STATUS,
NODE_STATUS_CHOICES_DICT,
NODEGROUP_STATUS,
+ NODEGROUPINTERFACE_MANAGEMENT,
)
from maasserver.exceptions import Unauthorized
from maasserver.fields import mac_error_msg
@@ -61,6 +63,7 @@
MACAddress,
Node,
NodeGroup,
+ NodeGroupInterface,
)
from maasserver.models.user import (
create_auth_token,
@@ -2377,7 +2380,12 @@
json.loads(response.content)["purpose"])
-class TestNodeGroupsAPI(AnonAPITestCase):
+class TestNodeGroupsAPI(APIv10TestMixin, MultipleUsersScenarios, TestCase):
+ scenarios = [
+ ('anon', dict(userfactory=lambda: AnonymousUser())),
+ ('user', dict(userfactory=factory.make_user)),
+ ('admin', dict(userfactory=factory.make_admin)),
+ ]
resources = (
('celery', FixtureResource(CeleryFixture())),
@@ -2390,9 +2398,23 @@
def test_nodegroups_index_lists_nodegroups(self):
# The nodegroups index lists node groups for the MAAS.
nodegroup = factory.make_node_group()
- response = self.client.get(reverse('nodegroups_handler'))
+ response = self.client.get(
+ reverse('nodegroups_handler'), {'op': 'list'})
self.assertEqual(httplib.OK, response.status_code)
- self.assertIn(nodegroup.uuid, json.loads(response.content))
+ self.assertEqual(
+ [{
+ 'uuid': nodegroup.uuid,
+ 'status': nodegroup.status,
+ 'name': nodegroup.name,
+ }],
+ json.loads(response.content))
+
+
+class TestAnonNodeGroupsAPI(AnonAPITestCase):
+
+ resources = (
+ ('celery', FixtureResource(CeleryFixture())),
+ )
def test_refresh_calls_refresh_worker(self):
nodegroup = factory.make_node_group()
@@ -2436,6 +2458,32 @@
# validated by an admin.
self.assertEqual(httplib.ACCEPTED, response.status_code)
+ def test_register_accepts_only_one_managed_interface(self):
+ name = factory.make_name('name')
+ uuid = factory.getRandomUUID()
+ # This will try to create 2 "managed" interfaces.
+ interface1 = make_interface_settings()
+ interface1['management'] = NODEGROUPINTERFACE_MANAGEMENT.DHCP
+ interface2 = interface1.copy()
+ response = self.client.post(
+ reverse('nodegroups_handler'),
+ {
+ 'op': 'register',
+ 'name': name,
+ 'uuid': uuid,
+ 'interfaces': json.dumps([interface1, interface2]),
+ })
+ self.assertEqual(
+ (
+ httplib.BAD_REQUEST,
+ {'interfaces':
+ [
+ "Only one managed interface can be configured for "
+ "this nodegroup"
+ ]},
+ ),
+ (response.status_code, json.loads(response.content)))
+
def test_register_nodegroup_validates_data(self):
response = self.client.post(
reverse('nodegroups_handler'),
@@ -2507,6 +2555,102 @@
self.assertEqual({'BROKER_URL': fake_broker_url}, parsed_result)
+def get_dict(object, fields):
+ """Return a dict of a subset of the fields/values of an object."""
+ return {
+ field: value for field, value in object.__dict__.items()
+ if field in fields
+ }
+
+
+class TestNodeGroupInterfacesAPI(APITestCase):
+
+ def test_list_lists_interfaces(self):
+ self.become_admin()
+ nodegroup = factory.make_node_group()
+ response = self.client.get(
+ reverse('nodegroupinterfaces_handler', args=[nodegroup.uuid]),
+ {'op': 'list'})
+ self.assertEqual(httplib.OK, response.status_code)
+ self.assertEqual(
+ [
+ get_dict(interface, DISPLAYED_NODEGROUP_FIELDS)
+ for interface in nodegroup.nodegroupinterface_set.all()
+ ],
+ json.loads(response.content))
+
+ def test_list_only_available_to_admin(self):
+ nodegroup = factory.make_node_group()
+ response = self.client.get(
+ reverse('nodegroupinterfaces_handler', args=[nodegroup.uuid]),
+ {'op': 'list'})
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_new_creates_interface(self):
+ self.become_admin()
+ nodegroup = factory.make_node_group(
+ management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
+
+ settings = make_interface_settings()
+ query_data = settings.copy()
+ query_data['op'] = 'new'
+ response = self.client.post(
+ reverse('nodegroupinterfaces_handler', args=[nodegroup.uuid]),
+ query_data)
+ self.assertEqual(httplib.OK, response.status_code, response.content)
+ expected_result = settings.copy()
+ new_interface = NodeGroupInterface.objects.get(
+ nodegroup=nodegroup, interface=settings['interface'])
+ self.assertThat(
+ new_interface,
+ MatchesStructure.byEquality(**expected_result))
+
+ def test_new_validates_data(self):
+ self.become_admin()
+ nodegroup = factory.make_node_group()
+ response = self.client.get(
+ reverse('nodegroupinterfaces_handler', args=[nodegroup.uuid]),
+ {'op': 'new', 'ip': 'invalid ip'})
+ self.assertEqual(httplib.BAD_REQUEST, response.status_code)
+
+ def test_new_only_available_to_admin(self):
+ nodegroup = factory.make_node_group()
+ response = self.client.get(
+ reverse('nodegroupinterfaces_handler', args=[nodegroup.uuid]),
+ {'op': 'new'})
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+
+class TestNodeGroupInterfaceAPI(APITestCase):
+
+ def test_read_interface(self):
+ self.become_admin()
+ nodegroup = factory.make_node_group()
+ interface = nodegroup.get_managed_interface()
+ response = self.client.get(
+ reverse(
+ 'nodegroupinterface_handler',
+ args=[nodegroup.uuid, interface.interface]))
+ self.assertEqual(httplib.OK, response.status_code)
+ self.assertEqual(
+ get_dict(interface, DISPLAYED_NODEGROUP_FIELDS),
+ json.loads(response.content))
+
+ def test_update_interface(self):
+ self.become_admin()
+ nodegroup = factory.make_node_group()
+ interface = nodegroup.get_managed_interface()
+ new_ip_range_high = factory.getRandomIPAddress()
+ response = self.client.put(
+ reverse(
+ 'nodegroupinterface_handler',
+ args=[nodegroup.uuid, interface.interface]),
+ {'ip_range_high': new_ip_range_high})
+ self.assertEqual(
+ (httplib.OK, new_ip_range_high),
+ (response.status_code, reload_object(interface).ip_range_high))
+
+
def explain_unexpected_response(expected_status, response):
"""Return human-readable failure message: unexpected http response."""
return "Unexpected http status (expected %s): %s - %s" % (
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2012-09-14 13:09:39 +0000
+++ src/maasserver/tests/test_forms.py 2012-09-18 07:59:21 +0000
@@ -585,19 +585,19 @@
'router_ip': factory.getRandomIPAddress(),
'ip_range_low': factory.getRandomIPAddress(),
'ip_range_high': factory.getRandomIPAddress(),
+ 'management': factory.getRandomEnum(NODEGROUPINTERFACE_MANAGEMENT),
}
class TestNodeGroupInterfaceForm(TestCase):
- def test_NodeGroupInterfaceForm_uses_initial_parameters(self):
- nodegroup = factory.make_node_group()
- management = factory.getRandomEnum(NODEGROUPINTERFACE_MANAGEMENT)
+ def test_NodeGroupInterfaceForm_save_sets_nodegroup(self):
+ nodegroup = factory.make_node_group(
+ management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
form = NodeGroupInterfaceForm(data=make_interface_settings())
- interface = form.save(nodegroup=nodegroup, management=management)
- self.assertEqual(
- (nodegroup, management),
- (interface.nodegroup, interface.management))
+ self.assertTrue(form.is_valid(), form._errors)
+ interface = form.save(nodegroup=nodegroup)
+ self.assertEqual(nodegroup, interface.nodegroup)
def test_NodeGroupInterfaceForm_validates_parameters(self):
form = NodeGroupInterfaceForm(data={'ip': factory.getRandomString()})
@@ -679,25 +679,60 @@
interfaces = json.dumps([interface])
form = NodeGroupWithInterfacesForm(
data={'name': name, 'uuid': uuid, 'interfaces': interfaces})
- self.assertTrue(form.is_valid())
+ self.assertTrue(form.is_valid(), form._errors)
form.save()
nodegroup = NodeGroup.objects.get(uuid=uuid)
self.assertThat(
nodegroup.nodegroupinterface_set.all()[0],
MatchesStructure.byEquality(**interface))
- self.assertEqual(
- NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED,
- nodegroup.nodegroupinterface_set.all()[0].management)
+
+ def test_form_checks_presence_of_other_managed_interfaces(self):
+ name = factory.make_name('name')
+ uuid = factory.getRandomUUID()
+ interfaces = []
+ for index in range(2):
+ interface = make_interface_settings()
+ interface['management'] = factory.getRandomEnum(
+ NODEGROUPINTERFACE_MANAGEMENT,
+ but_not=(NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED, ))
+ interfaces.append(interface)
+ interfaces = json.dumps(interfaces)
+ form = NodeGroupWithInterfacesForm(
+ data={'name': name, 'uuid': uuid, 'interfaces': interfaces})
+ self.assertFalse(form.is_valid())
+ self.assertIn(
+ "Only one managed interface can be configured for this nodegroup",
+ form._errors['interfaces'][0])
def test_NodeGroupWithInterfacesForm_creates_multiple_interfaces(self):
name = factory.make_name('name')
uuid = factory.getRandomUUID()
interface1 = make_interface_settings()
+ # Only one interface at most can be 'managed'.
interface2 = make_interface_settings()
+ interface2['management'] = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED
interfaces = json.dumps([interface1, interface2])
form = NodeGroupWithInterfacesForm(
data={'name': name, 'uuid': uuid, 'interfaces': interfaces})
- self.assertTrue(form.is_valid())
+ self.assertTrue(form.is_valid(), form._errors)
form.save()
nodegroup = NodeGroup.objects.get(uuid=uuid)
self.assertEqual(2, nodegroup.nodegroupinterface_set.count())
+
+ def test_NodeGroupWithInterfacesForm_creates_unmanaged_interfaces(self):
+ name = factory.make_name('name')
+ uuid = factory.getRandomUUID()
+ interface = make_interface_settings()
+ del interface['management']
+ interfaces = json.dumps([interface])
+ form = NodeGroupWithInterfacesForm(
+ data={'name': name, 'uuid': uuid, 'interfaces': interfaces})
+ self.assertTrue(form.is_valid(), form._errors)
+ form.save()
+ nodegroup = NodeGroup.objects.get(uuid=uuid)
+ self.assertEqual(
+ [NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED],
+ [
+ nodegroup.management for nodegroup in
+ nodegroup.nodegroupinterface_set.all()
+ ])
=== modified file 'src/maasserver/urls_api.py'
--- src/maasserver/urls_api.py 2012-09-18 07:59:20 +0000
+++ src/maasserver/urls_api.py 2012-09-18 07:59:21 +0000
@@ -25,6 +25,8 @@
FilesHandler,
MAASHandler,
NodeGroupHandler,
+ NodeGroupInterfaceHandler,
+ NodeGroupInterfacesHandler,
NodeGroupsHandler,
NodeHandler,
NodeMacHandler,
@@ -53,7 +55,10 @@
# Admin handlers.
maas_handler = AdminRestrictedResource(MAASHandler, authentication=api_auth)
-
+nodegroupinterface_handler = AdminRestrictedResource(
+ NodeGroupInterfaceHandler, authentication=api_auth)
+nodegroupinterfaces_handler = AdminRestrictedResource(
+ NodeGroupInterfacesHandler, authentication=api_auth)
# API URLs accessible to anonymous users.
urlpatterns = patterns('',
@@ -80,6 +85,10 @@
r'nodegroups/(?P<uuid>[^/]+)/$',
nodegroup_handler, name='nodegroup_handler'),
url(r'nodegroups/$', nodegroups_handler, name='nodegroups_handler'),
+ url(r'nodegroups/(?P<uuid>[^/]+)/interfaces/$',
+ nodegroupinterfaces_handler, name='nodegroupinterfaces_handler'),
+ url(r'nodegroups/(?P<uuid>[^/]+)/interfaces/(?P<interface>[^/]+)/$',
+ nodegroupinterface_handler, name='nodegroupinterface_handler'),
url(r'files/$', files_handler, name='files_handler'),
url(r'account/$', account_handler, name='account_handler'),
url(r'boot-images/$', boot_images_handler, name='boot_images_handler'),