← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/maas/prod-deps-not-in-buildout-precise into lp:maas

 

Gavin Panella has proposed merging lp:~allenap/maas/prod-deps-not-in-buildout-precise into lp:maas.

Commit message:
Removes the management of production dependencies from the purview of buildout.

Dependencies that form the final product will no longer be version controlled by buildout. They are assumed to be available on the system already. This is taken care of by `make install-dependencies`. The only dependencies managed by buildout are for testing and development.

Requested reviews:
  MAAS Maintainers (maas-maintainers)
Related bugs:
  Bug #1055235 in MAAS: "Too easy to test with different dependencies versus production ones"
  https://bugs.launchpad.net/maas/+bug/1055235

For more details, see:
https://code.launchpad.net/~allenap/maas/prod-deps-not-in-buildout-precise/+merge/125972
-- 
https://code.launchpad.net/~allenap/maas/prod-deps-not-in-buildout-precise/+merge/125972
Your team MAAS Maintainers is requested to review the proposed merge of lp:~allenap/maas/prod-deps-not-in-buildout-precise into lp:maas.
=== modified file 'buildout.cfg'
--- buildout.cfg	2012-09-22 15:38:25 +0000
+++ buildout.cfg	2012-09-24 10:48:57 +0000
@@ -33,7 +33,10 @@
 test-eggs =
   coverage
   fixtures
+<<<<<<< TREE
   lxml
+=======
+>>>>>>> MERGE-SOURCE
   mock
   nose
   nose-subunit

=== modified file 'etc/celeryconfig.py'
=== modified file 'required-packages/base'
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py	2012-09-21 16:36:27 +0000
+++ src/maasserver/api.py	2012-09-24 10:48:57 +0000
@@ -62,6 +62,13 @@
     "BootImagesHandler",
     "FilesHandler",
     "get_oauth_token",
+<<<<<<< TREE
+=======
+    "AccountHandler",
+    "AnonNodesHandler",
+    "BootImagesHandler",
+    "FilesHandler",
+>>>>>>> MERGE-SOURCE
     "NodeGroupsHandler",
     "NodeGroupInterfaceHandler",
     "NodeGroupInterfacesHandler",
@@ -104,6 +111,7 @@
 from docutils import core
 from formencode import validators
 from formencode.validators import Invalid
+<<<<<<< TREE
 from maasserver.apidoc import (
     describe_handler,
     find_api_handlers,
@@ -114,6 +122,13 @@
     discard_persistent_error,
     register_persistent_error,
     )
+=======
+from maasserver.components import (
+    COMPONENT,
+    discard_persistent_error,
+    register_persistent_error,
+    )
+>>>>>>> MERGE-SOURCE
 from maasserver.enum import (
     ARCHITECTURE,
     NODE_PERMISSION,
@@ -938,6 +953,7 @@
         NodeGroup.objects.refresh_workers()
         return HttpResponse("Sending worker refresh.", status=httplib.OK)
 
+<<<<<<< TREE
     @api_exported('POST')
     def register(self, request):
         """Register a new `NodeGroup`.
@@ -1049,6 +1065,11 @@
 
 def check_nodegroup_access(request, nodegroup):
     """Validate API access by worker for `nodegroup`.
+=======
+
+def check_nodegroup_access(request, nodegroup):
+    """Validate API access by worker for `nodegroup`.
+>>>>>>> MERGE-SOURCE
 
     This supports a nodegroup worker accessing its nodegroup object on
     the API.  If the request is done by anyone but the worker for this
@@ -1400,6 +1421,7 @@
     return HttpResponse(
         json.dumps(params._asdict()),
         content_type="application/json")
+<<<<<<< TREE
 
 
 @api_operations
@@ -1459,3 +1481,46 @@
     return HttpResponse(
         json.dumps(description),
         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.
+        """
+        check_nodegroup_access(request, NodeGroup.objects.ensure_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'])
+
+        if len(images) == 0:
+            warning = dedent("""\
+                No boot images have been imported yet.  Either the
+                maas-import-pxe-files script has not run yet, or it failed.
+
+                Try running it manually.  If it succeeds, this message will
+                go away within 5 minutes.
+                """)
+            register_persistent_error(COMPONENT.IMPORT_PXE_FILES, warning)
+        else:
+            discard_persistent_error(COMPONENT.IMPORT_PXE_FILES)
+
+        return HttpResponse("OK")
+>>>>>>> MERGE-SOURCE

=== added file 'src/maasserver/migrations/0023_add_bootimage_model.py'
--- src/maasserver/migrations/0023_add_bootimage_model.py	1970-01-01 00:00:00 +0000
+++ src/maasserver/migrations/0023_add_bootimage_model.py	2012-09-24 10:48:57 +0000
@@ -0,0 +1,182 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'BootImage'
+        db.create_table(u'maasserver_bootimage', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('architecture', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('subarchitecture', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('release', self.gf('django.db.models.fields.CharField')(max_length=255)),
+            ('purpose', self.gf('django.db.models.fields.CharField')(max_length=255)),
+        ))
+        db.send_create_signal(u'maasserver', ['BootImage'])
+
+        # Adding unique constraint on 'BootImage', fields ['architecture', 'subarchitecture', 'release', 'purpose']
+        db.create_unique(u'maasserver_bootimage', ['architecture', 'subarchitecture', 'release', 'purpose'])
+
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'BootImage', fields ['architecture', 'subarchitecture', 'release', 'purpose']
+        db.delete_unique(u'maasserver_bootimage', ['architecture', 'subarchitecture', 'release', 'purpose'])
+
+        # Deleting model 'BootImage'
+        db.delete_table(u'maasserver_bootimage')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'maasserver.bootimage': {
+            'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
+            'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'maasserver.config': {
+            'Meta': {'object_name': 'Config'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
+        },
+        u'maasserver.dhcplease': {
+            'Meta': {'object_name': 'DHCPLease'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
+            'mac': ('maasserver.fields.MACAddressField', [], {}),
+            'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
+        },
+        u'maasserver.filestorage': {
+            'Meta': {'object_name': 'FileStorage'},
+            'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}),
+            'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        u'maasserver.macaddress': {
+            'Meta': {'object_name': 'MACAddress'},
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
+            'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'maasserver.node': {
+            'Meta': {'object_name': 'Node'},
+            'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386'", 'max_length': '10'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+            'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
+            'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
+            'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
+            'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-620f90c0-fd7a-11e1-a967-3c970e0e56dc'", 'unique': 'True', 'max_length': '41'}),
+            'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {})
+        },
+        u'maasserver.nodegroup': {
+            'Meta': {'object_name': 'NodeGroup'},
+            'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
+            'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}),
+            'broadcast_ip': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'dhcp_interfaces': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+            'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip_range_high': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'ip_range_low': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'router_ip': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'subnet_mask': ('django.db.models.fields.IPAddressField', [], {'default': "u''", 'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}),
+            'worker_ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'})
+        },
+        u'maasserver.sshkey': {
+            'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
+            'created': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.TextField', [], {}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        u'maasserver.userprofile': {
+            'Meta': {'object_name': 'UserProfile'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
+        },
+        'piston.consumer': {
+            'Meta': {'object_name': 'Consumer'},
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"})
+        },
+        'piston.token': {
+            'Meta': {'object_name': 'Token'},
+            'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
+            'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1347523786L'}),
+            'token_type': ('django.db.models.fields.IntegerField', [], {}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}),
+            'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
+        }
+    }
+
+    complete_apps = ['maasserver']

=== renamed file 'src/maasserver/migrations/0023_add_bootimage_model.py' => 'src/maasserver/migrations/0023_add_bootimage_model.py.moved'
=== modified file 'src/maasserver/models/__init__.py'
=== modified file 'src/maasserver/testing/factory.py'
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py	2012-09-21 16:36:27 +0000
+++ src/maasserver/tests/test_api.py	2012-09-24 10:48:57 +0000
@@ -2897,6 +2897,14 @@
     return user
 
 
+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."""
 
@@ -2936,6 +2944,7 @@
         self.assertEqual(
             httplib.FORBIDDEN, response.status_code,
             explain_unexpected_response(httplib.FORBIDDEN, response))
+<<<<<<< TREE
 
 
 class TestBootImagesAPI(APITestCase):
@@ -3051,3 +3060,96 @@
         description = json.loads(response.content)
         self.assertSetEqual({"doc", "handlers"}, set(description))
         self.assertIsInstance(description["handlers"], list)
+=======
+
+
+class TestBootImagesAPI(APITestCase):
+
+    resources = (
+        ('celery', FixtureResource(CeleryFixture())),
+        )
+
+    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 = make_boot_image_params()
+        client = make_worker_client(NodeGroup.objects.ensure_master())
+        response = self.report_images([image], client=client)
+        self.assertEqual(
+            (httplib.OK, "OK"),
+            (response.status_code, response.content))
+        self.assertTrue(
+            BootImage.objects.have_image(**image))
+
+    def test_report_boot_images_ignores_unknown_image_properties(self):
+        image = make_boot_image_params()
+        image['nonesuch'] = factory.make_name('nonesuch'),
+        client = make_worker_client(NodeGroup.objects.ensure_master())
+        response = self.report_images([image], client=client)
+        self.assertEqual(
+            (httplib.OK, "OK"),
+            (response.status_code, response.content))
+
+    def test_report_boot_images_warns_if_no_images_found(self):
+        recorder = self.patch(api, 'register_persistent_error')
+        client = make_worker_client(NodeGroup.objects.ensure_master())
+
+        response = self.report_images([], client=client)
+        self.assertEqual(
+            (httplib.OK, "OK"),
+            (response.status_code, response.content))
+
+        self.assertIn(
+            COMPONENT.IMPORT_PXE_FILES,
+            [args[0][0] for args in recorder.call_args_list])
+
+    def test_report_boot_images_removes_warning_if_images_found(self):
+        self.patch(api, 'register_persistent_error')
+        self.patch(api, 'discard_persistent_error')
+        client = make_worker_client(NodeGroup.objects.ensure_master())
+
+        response = self.report_images(
+            [make_boot_image_params()], client=client)
+        self.assertEqual(
+            (httplib.OK, "OK"),
+            (response.status_code, response.content))
+
+        self.assertItemsEqual(
+            [],
+            api.register_persistent_error.call_args_list)
+        api.discard_persistent_error.assert_called_once_with(
+            COMPONENT.IMPORT_PXE_FILES)
+
+    def test_worker_calls_report_boot_images(self):
+        refresh_worker(NodeGroup.objects.ensure_master())
+        self.patch(MAASClient, 'post')
+        self.patch(tftppath, 'list_boot_images', Mock(return_value=[]))
+
+        tasks.report_boot_images.delay()
+
+        MAASClient.post.assert_called_once_with(
+            reverse('boot_images_handler').lstrip('/'), 'report_boot_images',
+            images=json.dumps([]))
+>>>>>>> MERGE-SOURCE

=== modified file 'src/maasserver/urls_api.py'
--- src/maasserver/urls_api.py	2012-09-18 07:51:29 +0000
+++ src/maasserver/urls_api.py	2012-09-24 10:48:57 +0000
@@ -20,8 +20,12 @@
     AccountHandler,
     AdminRestrictedResource,
     api_doc,
+<<<<<<< TREE
     BootImagesHandler,
     describe,
+=======
+    BootImagesHandler,
+>>>>>>> MERGE-SOURCE
     FilesHandler,
     MAASHandler,
     NodeGroupHandler,
@@ -47,10 +51,19 @@
     NodeMacsHandler, authentication=api_auth)
 nodegroup_handler = RestrictedResource(
     NodeGroupHandler, authentication=api_auth)
+<<<<<<< TREE
 nodegroups_handler = RestrictedResource(
     NodeGroupsHandler, authentication=api_auth)
 boot_images_handler = RestrictedResource(
     BootImagesHandler, 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.
+nodegroups_handler = Resource(NodeGroupsHandler)
+>>>>>>> MERGE-SOURCE
 
 
 # Admin handlers.

=== modified file 'src/provisioningserver/tasks.py'
--- src/provisioningserver/tasks.py	2012-09-17 11:45:35 +0000
+++ src/provisioningserver/tasks.py	2012-09-24 10:48:57 +0000
@@ -29,11 +29,16 @@
     )
 
 from celery.task import task
+<<<<<<< TREE
 from celeryconfig import (
     DHCP_CONFIG_FILE,
     DHCP_INTERFACES_FILE,
     )
 from provisioningserver import boot_images
+=======
+from celeryconfig import DHCP_CONFIG_FILE
+from provisioningserver import boot_images
+>>>>>>> MERGE-SOURCE
 from provisioningserver.auth import (
     record_api_credentials,
     record_maas_url,
@@ -321,6 +326,7 @@
 @task
 def restart_dhcp_server():
     """Restart the DHCP server."""
+<<<<<<< TREE
     check_call(['sudo', '-n', 'service', 'maas-dhcp-server', 'restart'])
 
 
@@ -333,3 +339,17 @@
 def report_boot_images():
     """For master worker only: report available netboot images."""
     boot_images.report_to_server()
+=======
+    check_call(['sudo', 'service', 'isc-dhcp-server', 'restart'])
+
+
+# =====================================================================
+# Boot images-related tasks
+# =====================================================================
+
+
+@task
+def report_boot_images():
+    """For master worker only: report available netboot images."""
+    boot_images.report_to_server()
+>>>>>>> MERGE-SOURCE

=== modified file 'src/provisioningserver/tests/test_maas_import_pxe_files.py'
=== modified file 'src/provisioningserver/tests/test_tasks.py'
--- src/provisioningserver/tests/test_tasks.py	2012-09-17 11:53:03 +0000
+++ src/provisioningserver/tests/test_tasks.py	2012-09-24 10:48:57 +0000
@@ -22,12 +22,18 @@
     )
 
 from apiclient.creds import convert_tuple_to_string
+<<<<<<< TREE
 from apiclient.maas_client import MAASClient
 from apiclient.testing.credentials import make_api_credentials
 from celeryconfig import (
     DHCP_CONFIG_FILE,
     DHCP_INTERFACES_FILE,
     )
+=======
+from apiclient.maas_client import MAASClient
+from apiclient.testing.credentials import make_api_credentials
+from celeryconfig import DHCP_CONFIG_FILE
+>>>>>>> MERGE-SOURCE
 from maastesting.celery import CeleryFixture
 from maastesting.factory import factory
 from maastesting.fakemethod import (

=== modified file 'versions.cfg'
--- versions.cfg	2012-09-22 15:38:25 +0000
+++ versions.cfg	2012-09-24 10:48:57 +0000
@@ -9,7 +9,10 @@
 Jinja2 = 2.6
 Pygments = 1.4
 Sphinx = 1.1.3
+<<<<<<< TREE
 lxml = 2.3.5
+=======
+>>>>>>> MERGE-SOURCE
 
 [versions-dev]
 # Bug 251 is problematic in 0.9.2.