launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #11794
[Merge] lp:~jtv/maas/bug-1025582-api into lp:maas
Jeroen T. Vermeulen has proposed merging lp:~jtv/maas/bug-1025582-api into lp:maas with lp:~jtv/maas/bug-1025582-model as a prerequisite.
Requested reviews:
MAAS Maintainers (maas-maintainers)
For more details, see:
https://code.launchpad.net/~jtv/maas/bug-1025582-api/+merge/123711
I did not have a separate pre-imp call for this part of the work, although I did discuss the approach (including having an API call along these lines) with Julian.
Jeroen
--
https://code.launchpad.net/~jtv/maas/bug-1025582-api/+merge/123711
Your team MAAS Maintainers is requested to review the proposed merge of lp:~jtv/maas/bug-1025582-api into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-09-03 04:59:58 +0000
+++ src/maasserver/api.py 2012-09-11 10:28:19 +0000
@@ -56,6 +56,7 @@
__all__ = [
"api_doc",
"api_doc_title",
+ "BootImagesHandler",
"generate_api_doc",
"get_oauth_token",
"AccountHandler",
@@ -117,6 +118,7 @@
get_node_edit_form,
)
from maasserver.models import (
+ BootImage,
Config,
DHCPLease,
FileStorage,
@@ -1013,6 +1015,20 @@
value = Config.objects.get_config(name)
return HttpResponse(json.dumps(value), content_type='application/json')
+ @api_exported('POST')
+ def report_boot_images(self, request):
+ """Report available boot images.
+
+ A boot image consists of a kernel and initrd, which a netbooting
+ node can download from TFTP (as directed over PXE). These are
+ downloaded by the `maas-import-pxe-files` script, running on the
+ same system as the master worker. The master worker can report
+ to the server which boot images are available to nodes.
+
+ :param images: A list of tuples: (architecture, sub-architecture,
+ release, purpose).
+ """
+
# Title section for the API documentation. Matches in style, format,
# etc. whatever generate_api_doc() produces, so that you can concatenate
@@ -1154,3 +1170,30 @@
return HttpResponse(
json.dumps(params._asdict()),
content_type="application/json")
+
+
+@api_operations
+class BootImagesHandler(BaseHandler):
+
+ @classmethod
+ def resource_uri(cls):
+ return ('boot_images_handler', [])
+
+ @api_exported('POST')
+ def report_boot_images(self, request):
+ """Report images available to net-boot nodes from.
+
+ :param images: A list of dicts, each describing a boot image with
+ these properties: `architecture`, `subarchitecture`, `release`,
+ `purpose`, all as in the code that determines TFTP paths for
+ these images.
+ """
+ get_nodegroup_for_worker(request, 'master')
+ images = json.loads(get_mandatory_param(request.data, 'images'))
+ for image in images:
+ BootImage.objects.register_image(
+ architecture=image['architecture'],
+ subarchitecture=image.get('subarchitecture', 'generic'),
+ release=image['release'],
+ purpose=image['purpose'])
+ return HttpResponse("Images noted.")
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-09-05 13:30:21 +0000
+++ src/maasserver/tests/test_api.py 2012-09-11 10:28:19 +0000
@@ -52,6 +52,7 @@
from maasserver.exceptions import Unauthorized
from maasserver.fields import mac_error_msg
from maasserver.models import (
+ BootImage,
Config,
DHCPLease,
MACAddress,
@@ -2547,15 +2548,17 @@
nodegroup_path, 'update_leases', leases=json.dumps(leases))
+def log_in_as_normal_user(client):
+ """Log `client` in as a normal user."""
+ password = factory.getRandomString()
+ user = factory.make_user(password=password)
+ client.login(username=user.username, password=password)
+ return user
+
+
class TestNodeGroupAPIAuth(APIv10TestMixin, TestCase):
"""Authorization tests for nodegroup API."""
- def log_in_as_normal_user(self):
- """Log `self.client` in as a normal user."""
- password = factory.getRandomString()
- user = factory.make_user(password=password)
- self.client.login(username=user.username, password=password)
-
def test_nodegroup_requires_authentication(self):
nodegroup = factory.make_node_group()
response = self.client.get(
@@ -2574,7 +2577,7 @@
def test_update_leases_does_not_work_for_normal_user(self):
nodegroup = factory.make_node_group()
- self.log_in_as_normal_user()
+ log_in_as_normal_user(self.client)
response = self.client.post(
reverse('nodegroup_handler', args=[nodegroup.name]),
{'op': 'update_leases', 'leases': json.dumps({})})
@@ -2592,3 +2595,59 @@
self.assertEqual(
httplib.FORBIDDEN, response.status_code,
explain_unexpected_response(httplib.FORBIDDEN, response))
+
+
+class TestBootImagesAPI(APITestCase):
+
+ def report_images(self, images, client=None):
+ if client is None:
+ client = self.client
+ return client.post(
+ reverse('boot_images_handler'),
+ {'op': 'report_boot_images', 'images': json.dumps(images)})
+
+ def test_report_boot_images_does_not_work_for_normal_user(self):
+ NodeGroup.objects.ensure_master()
+ log_in_as_normal_user(self.client)
+ response = self.report_images([])
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_report_boot_images_works_for_master_worker(self):
+ client = make_worker_client(NodeGroup.objects.ensure_master())
+ response = self.report_images([], client=client)
+ self.assertEqual(httplib.OK, response.status_code)
+
+ def test_report_boot_images_does_not_work_for_other_workers(self):
+ NodeGroup.objects.ensure_master()
+ client = make_worker_client(factory.make_node_group())
+ response = self.report_images([], client=client)
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_report_boot_images_stores_images(self):
+ image = {
+ 'architecture': factory.make_name('architecture'),
+ 'subarchitecture': factory.make_name('subarchitecture'),
+ 'release': factory.make_name('release'),
+ 'purpose': factory.make_name('purpose'),
+ }
+ client = make_worker_client(NodeGroup.objects.ensure_master())
+ response = self.report_images([image], client=client)
+ self.assertEqual(
+ (httplib.OK, "Images noted."),
+ (response.status_code, response.content))
+ self.assertTrue(
+ BootImage.objects.have_image(**image))
+
+ def test_report_boot_images_ignores_unknown_image_properties(self):
+ image = {
+ 'architecture': factory.make_name('architecture'),
+ 'subarchitecture': factory.make_name('subarchitecture'),
+ 'release': factory.make_name('release'),
+ 'purpose': factory.make_name('purpose'),
+ 'nonesuch': factory.make_name('nonesuch'),
+ }
+ client = make_worker_client(NodeGroup.objects.ensure_master())
+ response = self.report_images([image], client=client)
+ self.assertEqual(
+ (httplib.OK, "Images noted."),
+ (response.status_code, response.content))
=== modified file 'src/maasserver/urls_api.py'
--- src/maasserver/urls_api.py 2012-08-17 02:27:29 +0000
+++ src/maasserver/urls_api.py 2012-09-11 10:28:19 +0000
@@ -20,6 +20,7 @@
AccountHandler,
AdminRestrictedResource,
api_doc,
+ BootImagesHandler,
FilesHandler,
MAASHandler,
NodeGroupHandler,
@@ -44,6 +45,8 @@
NodeMacsHandler, authentication=api_auth)
nodegroup_handler = RestrictedResource(
NodeGroupHandler, authentication=api_auth)
+boot_images_handler = RestrictedResource(
+ BootImagesHandler, authentication=api_auth)
# The nodegroups view is anonymously accessible, but anonymous users
# can't drill down into individual nodegruops.
@@ -80,6 +83,7 @@
nodegroup_handler, name='nodegroup_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'),
)
Follow ups