launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06398
[Merge] lp:~jtv/maas/metadata-hostname into lp:maas
Jeroen T. Vermeulen has proposed merging lp:~jtv/maas/metadata-hostname into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jtv/maas/metadata-hostname/+merge/93457
This implements the “local-hostname” meta-data item as a lookup based on the Node as derived from the authentication key used for the request.
It's only one step along the way though. A few other things that need doing:
* Set up OAuth for the metadata service (probably by migrating this to Piston).
* Check that the request really is made by the node-init user.
* Creating node credentials and preseeding them through the provisioning server.
One step at a time.
Jeroen
--
https://code.launchpad.net/~jtv/maas/metadata-hostname/+merge/93457
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/maas/metadata-hostname into lp:maas.
=== modified file 'src/maas/settings.py'
--- src/maas/settings.py 2012-02-12 18:32:26 +0000
+++ src/maas/settings.py 2012-02-16 16:57:31 +0000
@@ -33,7 +33,7 @@
LOGOUT_URL = '/'
LOGIN_REDIRECT_URL = '/'
-API_URL_REGEXP = '^/api/'
+API_URL_REGEXP = '^/(api|metadata)/'
# We handle exceptions ourselves (in
# maasserver.middleware.APIErrorsMiddleware)
=== modified file 'src/maasserver/models.py'
--- src/maasserver/models.py 2012-02-16 16:50:40 +0000
+++ src/maasserver/models.py 2012-02-16 16:57:31 +0000
@@ -38,6 +38,7 @@
# Special users internal to MaaS.
SYSTEM_USERS = [
+ # For nodes' access to the metadata API:
nodeinituser.user_name,
]
=== modified file 'src/metadataserver/api.py'
--- src/metadataserver/api.py 2012-02-10 10:10:18 +0000
+++ src/metadataserver/api.py 2012-02-16 16:57:31 +0000
@@ -17,12 +17,41 @@
]
from django.http import HttpResponse
-
-
-class UnknownMetadataVersion(RuntimeError):
+from maasserver.exceptions import (
+ MaasAPINotFound,
+ Unauthorized,
+ )
+from metadataserver.models import NodeKey
+
+
+class UnknownMetadataVersion(MaasAPINotFound):
"""Not a known metadata version."""
+class UnknownNode(MaasAPINotFound):
+ """Not a known node."""
+
+
+def extract_oauth_key(auth_data):
+ """Extract the oauth key from auth data in HTTP header."""
+ for entry in auth_data.split():
+ key_value = entry.split('=', 1)
+ if len(key_value) == 2:
+ key, value = key_value
+ if key == 'oauth_token':
+ return value
+ raise Unauthorized("No oauth token found for metadata request.")
+
+
+def get_node_for_request(request):
+ """Return the `Node` that `request` is authorized to query for."""
+ auth_header = request.META.get('HTTP_AUTHORIZATION')
+ if auth_header is None:
+ raise Unauthorized("No authorization header received.")
+ key = extract_oauth_key(auth_header)
+ return NodeKey.objects.get_node_for_key(key)
+
+
def make_text_response(contents):
"""Create a response containing `contents` as plain text."""
return HttpResponse(contents, mimetype='text/plain')
@@ -50,13 +79,32 @@
return make_list_response(['meta-data', 'user-data'])
-def meta_data(request, version):
- """View: meta-data listing for a given version."""
+def retrieve_unknown_item(node, item_path):
+ """Retrieve meta-data: unknown sub-item."""
+ raise MaasAPINotFound("No such metadata item: %s" % '/'.join(item_path))
+
+
+def retrieve_local_hostname(node, item_path):
+ """Retrieve meta-data: local-hostname."""
+ return make_text_response(node.hostname)
+
+
+def meta_data(request, version, item=None):
+ """View: meta-data listing for a given version, or meta-data item."""
check_version(version)
- items = [
- 'kernel-id',
- ]
- return make_list_response(items)
+ node = get_node_for_request(request)
+
+ # Functions to retrieve meta-data items.
+ retrievers = {
+ 'local-hostname': retrieve_local_hostname,
+ }
+
+ if not item:
+ return make_list_response(sorted(retrievers.keys()))
+
+ item_path = item.split('/')
+ retriever = retrievers.get(item_path[0], retrieve_unknown_item)
+ return retriever(node, item_path[1:])
def user_data(request, version):
=== modified file 'src/metadataserver/tests/test_api.py'
--- src/metadataserver/tests/test_api.py 2012-02-10 10:10:18 +0000
+++ src/metadataserver/tests/test_api.py 2012-02-16 16:57:31 +0000
@@ -11,6 +11,9 @@
__metaclass__ = type
__all__ = []
+import httplib
+
+from maasserver.testing.factory import factory
from maastesting import TestCase
from metadataserver.api import (
check_version,
@@ -18,6 +21,7 @@
make_text_response,
UnknownMetadataVersion,
)
+from metadataserver.models import NodeKey
class TestHelpers(TestCase):
@@ -46,10 +50,16 @@
class TestViews(TestCase):
"""Tests for the API views."""
- def get(self, path):
+ def get(self, path, **headers):
# Root of the metadata API service.
metadata_root = "/metadata"
- return self.client.get(metadata_root + path)
+ return self.client.get(metadata_root + path, **headers)
+
+ def make_node_and_auth_header(self):
+ node = factory.make_node()
+ consumer, token = NodeKey.objects.create_token(node)
+ header = 'oauth_token=%s' % token.key
+ return node, header
def test_metadata_index_shows_latest(self):
self.assertIn('latest', self.get('/').content)
@@ -66,9 +76,24 @@
self.assertIn('user-data', items)
def test_meta_data_view_returns_text_response(self):
- self.assertEqual(
+ self.assertIn(
'text/plain', self.get('/latest/meta-data/')['Content-Type'])
+ def test_meta_data_unknown_item_is_not_found(self):
+ node, header = self.make_node_and_auth_header()
+ response = self.get(
+ '/latest/meta-data/UNKNOWN-ITEM-HA-HA-HA',
+ HTTP_AUTHORIZATION=header)
+ self.assertEqual(httplib.NOT_FOUND, response.status_code)
+
+ def test_meta_data_local_hostname(self):
+ node, header = self.make_node_and_auth_header()
+ response = self.get(
+ '/latest/meta-data/local-hostname', HTTP_AUTHORIZATION=header)
+ self.assertEqual(httplib.OK, response.status_code)
+ self.assertIn('text/plain', response['Content-Type'])
+ self.assertEqual(node.hostname, response.content)
+
def test_user_data_view_returns_binary_blob(self):
response = self.get('/latest/user-data')
self.assertEqual('application/octet-stream', response['Content-Type'])
=== modified file 'src/metadataserver/urls.py'
--- src/metadataserver/urls.py 2012-02-08 11:18:10 +0000
+++ src/metadataserver/urls.py 2012-02-16 16:57:31 +0000
@@ -28,7 +28,7 @@
urlpatterns = patterns(
'',
url(
- r'(?P<version>[^/]+)/meta-data/$', meta_data,
+ r'(?P<version>[^/]+)/meta-data/(?P<item>.*)$', meta_data,
name='metadata_meta_data'),
url(
r'(?P<version>[^/]+)/user-data$', user_data,