← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/launchpad/virtuality-restricted-bug-837975 into lp:launchpad

 

Raphaël Victor Badin has proposed merging lp:~rvb/launchpad/virtuality-restricted-bug-837975 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #837975 in Launchpad itself: "Distro +add and +edit page don't have settings for virtuality and restricted architectures"
  https://bugs.launchpad.net/launchpad/+bug/837975

For more details, see:
https://code.launchpad.net/~rvb/launchpad/virtuality-restricted-bug-837975/+merge/74120

This branch adds two fields to distro's +add and +edit pages. They will be used to tweak the virtuality and the restricted architectures of the distro's main archive (http://people.canonical.com/~rvb/distro_edit.png). The main changes of this branch (apart from UI changes) are the changes made to archive to allow changing enabled_restricted_families for main archives if the archive is set virtualized (require_virtualized is True).

= Tests =

./bin/test -vvc test_distribution_views test_add_distro_default_value_require_virtualized
./bin/test -vvc test_distribution_views test_add_distro_init_value_enabled_restricted_families
./bin/test -vvc test_distribution_views test_add_distro_require_virtualized
./bin/test -vvc test_distribution_views test_add_distro_enabled_restricted_families
./bin/test -vvc test_distribution_views test_add_distro_enabled_restricted_families_error
./bin/test -vvc test_distribution_views test_edit_distro_init_value_require_virtualized
./bin/test -vvc test_distribution_views test_edit_distro_init_value_enabled_restricted_families
./bin/test -vvc test_distribution_views test_change_require_virtualized
./bin/test -vvc test_distribution_views test_change_enabled_restricted_families
./bin/test -vvc test_distribution_views test_cannot_change_enabled_restricted_families

./bin/test -vvc test_archive test_main_archive_can_not_be_restricted_not_virtualized
./bin/test -vvc test_archive test_main_virtualized_archive_can_be_restricted

= QA =

Create a new distro
https://dogfood.launchpad.net/distros/+add and make sure the new fields are there. Make sure that "Enabled restricted families" can only be changed if "Require virtualized builders" is checked. Same test for https://dogfood.launchpad.net/mydistro/+edit.

-- 
https://code.launchpad.net/~rvb/launchpad/virtuality-restricted-bug-837975/+merge/74120
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/launchpad/virtuality-restricted-bug-837975 into lp:launchpad.
=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py	2011-06-16 13:50:58 +0000
+++ lib/lp/registry/browser/distribution.py	2011-09-05 17:01:29 +0000
@@ -41,11 +41,13 @@
 from collections import defaultdict
 import datetime
 
+from zope.app.form.browser.boolwidgets import CheckBoxWidget
 from zope.component import getUtility
 from zope.event import notify
 from zope.formlib import form
 from zope.interface import implements
 from zope.lifecycleevent import ObjectCreatedEvent
+from zope.schema import Bool
 from zope.security.interfaces import Unauthorized
 
 from canonical.launchpad.browser.feeds import FeedsMixin
@@ -82,6 +84,7 @@
     )
 from lp.app.errors import NotFoundError
 from lp.app.widgets.image import ImageChangeWidget
+from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
 from lp.archivepublisher.interfaces.publisherconfig import (
     IPublisherConfig,
     IPublisherConfigSet,
@@ -104,8 +107,8 @@
     IRegistryCollectionNavigationMenu,
     RegistryCollectionActionMenuBase,
     )
+from lp.registry.browser.objectreassignment import ObjectReassignmentView
 from lp.registry.browser.pillar import PillarBugsMenu
-from lp.registry.browser.objectreassignment import ObjectReassignmentView
 from lp.registry.interfaces.distribution import (
     IDerivativeDistribution,
     IDistribution,
@@ -123,9 +126,11 @@
     request_country,
     )
 from lp.services.propertycache import cachedproperty
+from lp.soyuz.browser.archive import EnableRestrictedFamiliesMixin
 from lp.soyuz.browser.packagesearch import PackageSearchViewBase
 from lp.soyuz.enums import ArchivePurpose
 from lp.soyuz.interfaces.archive import IArchiveSet
+from lp.soyuz.interfaces.processor import IProcessorFamilySet
 
 
 class DistributionNavigation(
@@ -772,7 +777,26 @@
         return self.context.count()
 
 
-class DistributionAddView(LaunchpadFormView):
+class RequireVirtualizedBuildersMixin:
+    """A mixin that provides require_virtualized field support"""
+
+    def createRequireVirtualized(self):
+        return form.Fields(
+            Bool(
+                __name__='require_virtualized',
+                title=u"Require virtualized builders",
+                description=(
+                    u"Only build the distribution's packages on virtual "
+                    "builders."),
+                required=True))
+
+    def updateRequireVirtualized(self, require_virtualized, archive):
+        archive.require_virtualized = require_virtualized
+
+
+class DistributionAddView(LaunchpadFormView,
+                          RequireVirtualizedBuildersMixin,
+                          EnableRestrictedFamiliesMixin):
 
     schema = IDistribution
     label = "Register a new distribution"
@@ -789,17 +813,40 @@
         "official_rosetta",
         "answers_usage",
         ]
+    custom_widget('require_virtualized', CheckBoxWidget)
+    custom_widget('enabled_restricted_families', LabeledMultiCheckBoxWidget)
 
     @property
     def page_title(self):
         """The page title."""
         return self.label
 
+    def validate(self, data):
+        self.validate_enabled_restricted_families(
+            data, ENABLED_RESTRICTED_FAMILITES_ERROR_MSG)
+
+    @property
+    def initial_values(self):
+        proc_family_set = getUtility(IProcessorFamilySet)
+        restricted_families = set(proc_family_set.getRestricted())
+        return {
+            'enabled_restricted_families': restricted_families,
+            'require_virtualized': False,
+            }
+
     @property
     def cancel_url(self):
         """See `LaunchpadFormView`."""
         return canonical_url(self.context)
 
+    def setUpFields(self):
+        """See `LaunchpadFormView`."""
+        LaunchpadFormView.setUpFields(self)
+        self.form_fields += self.createRequireVirtualized()
+        self.form_fields += self.createEnabledRestrictedFamilies(
+            u'The restricted architecture families on which the '
+            "distribution's main archive can build.")
+
     @action("Save", name='save')
     def save_action(self, action, data):
         distribution = getUtility(IDistributionSet).new(
@@ -813,11 +860,26 @@
             owner=self.user,
             registrant=self.user,
             )
+        archive = distribution.main_archive
+        self.updateRequireVirtualized(data['require_virtualized'], archive)
+        if archive.require_virtualized is True:
+            archive.enabled_restricted_families = (
+                data['enabled_restricted_families'])
+
         notify(ObjectCreatedEvent(distribution))
         self.next_url = canonical_url(distribution)
 
 
-class DistributionEditView(RegistryEditFormView):
+ENABLED_RESTRICTED_FAMILITES_ERROR_MSG = (
+    u"This distribution's main archive can not be restricted to "
+    'certain architectures unless the archive is also set '
+    'to build on virtualized builders '
+    "(see 'Require virtualized builders' above).")
+
+
+class DistributionEditView(RegistryEditFormView,
+                           RequireVirtualizedBuildersMixin,
+                           EnableRestrictedFamiliesMixin):
 
     schema = IDistribution
     field_names = [
@@ -841,12 +903,31 @@
     custom_widget('icon', ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
     custom_widget('logo', ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
     custom_widget('mugshot', ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+    custom_widget('require_virtualized', CheckBoxWidget)
+    custom_widget('enabled_restricted_families', LabeledMultiCheckBoxWidget)
 
     @property
     def label(self):
         """See `LaunchpadFormView`."""
         return 'Change %s details' % self.context.displayname
 
+    def setUpFields(self):
+        """See `LaunchpadFormView`."""
+        RegistryEditFormView.setUpFields(self)
+        self.form_fields += self.createRequireVirtualized()
+        self.form_fields += self.createEnabledRestrictedFamilies(
+            u'The restricted architecture families on which the '
+            "distribution's main archive can build.")
+
+    @property
+    def initial_values(self):
+        return {
+            'require_virtualized':
+                self.context.main_archive.require_virtualized,
+            'enabled_restricted_families':
+                self.context.main_archive.enabled_restricted_families,
+            }
+
     def validate(self, data):
         """Constrain bug expiration to Launchpad Bugs tracker."""
         # enable_bug_expiration is disabled by JavaScript when official_malone
@@ -856,6 +937,28 @@
         if not official_malone:
             data['enable_bug_expiration'] = False
 
+        # Validate enabled_restricted_families.
+        self.validate_enabled_restricted_families(
+            data,
+            ENABLED_RESTRICTED_FAMILITES_ERROR_MSG)
+
+    @action("Change", name='change')
+    def change_action(self, action, data):
+        # Update context.main_archive.
+        new_require_virtualized = data.get('require_virtualized')
+        if new_require_virtualized is not None:
+            self.updateRequireVirtualized(
+                new_require_virtualized, self.context.main_archive)
+            del(data['require_virtualized'])
+        new_enabled_restricted_families = data.get(
+            'enabled_restricted_families')
+        if new_enabled_restricted_families is not None:
+            self.context.main_archive.enabled_restricted_families = (
+                new_enabled_restricted_families)
+            del(data['enabled_restricted_families'])
+
+        self.updateContextFromData(data)
+
 
 class DistributionSeriesBaseView(LaunchpadView):
     """A base view to list distroseries."""

=== modified file 'lib/lp/registry/browser/tests/test_distribution_views.py'
--- lib/lp/registry/browser/tests/test_distribution_views.py	2011-03-16 17:14:26 +0000
+++ lib/lp/registry/browser/tests/test_distribution_views.py	2011-09-05 17:01:29 +0000
@@ -12,6 +12,7 @@
 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
 from lp.registry.browser.distribution import DistributionPublisherConfigView
 from lp.registry.interfaces.distribution import IDistributionSet
+from lp.soyuz.interfaces.processor import IProcessorFamilySet
 from lp.testing import (
     login_celebrity,
     TestCaseWithFactory,
@@ -83,7 +84,7 @@
 
     def test_change_existing_config(self):
         # Test POSTing to change existing config.
-        pubconf = self.factory.makePublisherConfig(
+        self.factory.makePublisherConfig(
             distribution=self.distro,
             root_dir=u"random",
             base_url=u"blah",
@@ -102,13 +103,13 @@
         self.owner = self.factory.makePerson()
         self.registrant = self.factory.makePerson()
         self.simple_user = self.factory.makePerson()
+        self.admin = login_celebrity('admin')
+        self.distributionset = getUtility(IDistributionSet)
+        proc_family_set = getUtility(IProcessorFamilySet)
+        self.restricted_families = proc_family_set.getRestricted()
 
-    def test_registrant_set_by_creation(self):
-        # The registrant field should be set to the Person creating
-        # the distribution.
-        admin = login_celebrity('admin')
-        distributionset = getUtility(IDistributionSet)
-        creation_form = {
+    def getDefaultAddDict(self):
+        return {
             'field.name': 'newbuntu',
             'field.displayname': 'newbuntu',
             'field.title': 'newbuntu',
@@ -116,14 +117,183 @@
             'field.description': 'newbuntu',
             'field.domainname': 'newbuntu',
             'field.members': self.simple_user.name,
+            'field.require_virtualized': '',
+            'field.enabled_restricted_families': [family.name
+                for family in self.restricted_families],
             'field.actions.save': 'Save',
             }
-        view = create_initialized_view(
-            distributionset, '+add', principal=admin,
-            method='POST', form=creation_form)
-        distribution = distributionset.getByName('newbuntu')
-        self.assertEqual(distribution.owner, admin)
-        self.assertEqual(distribution.registrant, admin)
+
+    def test_registrant_set_by_creation(self):
+        # The registrant field should be set to the Person creating
+        # the distribution.
+        creation_form = self.getDefaultAddDict()
+        create_initialized_view(
+            self.distributionset, '+add', principal=self.admin,
+            method='POST', form=creation_form)
+        distribution = self.distributionset.getByName('newbuntu')
+        self.assertEqual(distribution.owner, self.admin)
+        self.assertEqual(distribution.registrant, self.admin)
+
+    def test_add_distro_default_value_require_virtualized(self):
+        view = create_initialized_view(
+            self.distributionset, '+add', principal=self.admin,
+            method='GET')
+
+        widget = view.widgets['require_virtualized']
+        self.assertEqual(False, widget._getCurrentValue())
+
+    def test_add_distro_init_value_enabled_restricted_families(self):
+        view = create_initialized_view(
+            self.distributionset, '+add', principal=self.admin,
+            method='GET')
+
+        widget = view.widgets['enabled_restricted_families']
+        self.assertContentEqual(
+            self.restricted_families, widget._getCurrentValue())
+        self.assertContentEqual(
+            self.restricted_families,
+            [item.value for item in widget.vocabulary])
+
+    def test_add_distro_require_virtualized(self):
+        creation_form = self.getDefaultAddDict()
+        creation_form['field.require_virtualized'] = ''
+        create_initialized_view(
+            self.distributionset, '+add', principal=self.admin,
+            method='POST', form=creation_form)
+
+        distribution = self.distributionset.getByName('newbuntu')
+        self.assertEqual(
+            False,
+            distribution.main_archive.require_virtualized)
+
+    def test_add_distro_enabled_restricted_families(self):
+        creation_form = self.getDefaultAddDict()
+        creation_form['field.enabled_restricted_families'] = []
+        creation_form['field.require_virtualized'] = 'on'
+        create_initialized_view(
+            self.distributionset, '+add', principal=self.admin,
+            method='POST', form=creation_form)
+
+        distribution = self.distributionset.getByName('newbuntu')
+        self.assertEqual(
+            True,
+            distribution.main_archive.require_virtualized)
+        self.assertContentEqual(
+            [],
+            distribution.main_archive.enabled_restricted_families)
+
+    def test_add_distro_enabled_restricted_families_error(self):
+        # If require_virtualized is False, enabled_restricted_families
+        # cannot be changed.
+        creation_form = self.getDefaultAddDict()
+        creation_form['field.enabled_restricted_families'] = []
+        creation_form['field.require_virtualized'] = ''
+        view = create_initialized_view(
+            self.distributionset, '+add', principal=self.admin,
+            method='POST', form=creation_form)
+
+        self.assertEqual(
+            u"This distribution's main archive can not be restricted to "
+            "certain architectures unless the archive is also set to build "
+            "on virtualized builders (see 'Require virtualized builders' "
+            "above).",
+            view.widget_errors.get('enabled_restricted_families'))
+
+
+class TestDistroEditView(TestCaseWithFactory):
+    """Test the +edit page for a distribution."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestDistroEditView, self).setUp()
+        self.admin = login_celebrity('admin')
+        self.distribution = self.factory.makeDistribution()
+        proc_family_set = getUtility(IProcessorFamilySet)
+        self.restricted_families = proc_family_set.getRestricted()
+
+    def test_edit_distro_init_value_require_virtualized(self):
+        view = create_initialized_view(
+            self.distribution, '+edit', principal=self.admin,
+            method='GET')
+
+        widget = view.widgets['require_virtualized']
+        self.assertEqual(
+            self.distribution.main_archive.require_virtualized,
+            widget._getCurrentValue())
+
+    def test_edit_distro_init_value_enabled_restricted_families(self):
+        self.distribution.main_archive.require_virtualized = False
+        view = create_initialized_view(
+            self.distribution, '+edit', principal=self.admin,
+            method='GET')
+
+        widget = view.widgets['enabled_restricted_families']
+        self.assertContentEqual(
+            self.restricted_families, widget._getCurrentValue())
+        self.assertContentEqual(
+            self.restricted_families,
+            [item.value for item in widget.vocabulary])
+
+    def getDefaultEditDict(self):
+        return {
+            'field.displayname': 'newbuntu',
+            'field.title': 'newbuntu',
+            'field.summary': 'newbuntu',
+            'field.description': 'newbuntu',
+            'field.require_virtualized.used': u'',
+            'field.enabled_restricted_families': [family.name
+                for family in self.restricted_families],
+            'field.actions.change': 'Change',
+            }
+
+    def test_change_require_virtualized(self):
+        edit_form = self.getDefaultEditDict()
+        edit_form['field.require_virtualized'] = 'on'
+
+        self.distribution.main_archive.require_virtualized = False
+        create_initialized_view(
+            self.distribution, '+edit', principal=self.admin,
+            method='POST', form=edit_form)
+        self.assertEqual(
+            True,
+            self.distribution.main_archive.require_virtualized)
+
+    def test_change_enabled_restricted_families(self):
+        # If require_virtualized is True, enabled_restricted_families
+        # can be changed.
+        edit_form = self.getDefaultEditDict()
+        edit_form['field.require_virtualized'] = 'on'
+        edit_form['field.enabled_restricted_families'] = []
+
+        self.distribution.main_archive.require_virtualized = False
+        self.assertContentEqual(
+            self.restricted_families,
+            self.distribution.main_archive.enabled_restricted_families)
+        create_initialized_view(
+            self.distribution, '+edit', principal=self.admin,
+            method='POST', form=edit_form)
+
+        self.assertContentEqual(
+            [],
+            self.distribution.main_archive.enabled_restricted_families)
+
+    def test_cannot_change_enabled_restricted_families(self):
+        # If require_virtualized is False, enabled_restricted_families
+        # cannot be changed.
+        edit_form = self.getDefaultEditDict()
+        edit_form['field.require_virtualized'] = ''
+        edit_form['field.enabled_restricted_families'] = []
+
+        view = create_initialized_view(
+            self.distribution, '+edit', principal=self.admin,
+            method='POST', form=edit_form)
+        self.assertEqual(
+            u"This distribution's main archive can not be restricted to "
+            "certain architectures unless the archive is also set to build "
+            "on virtualized builders (see 'Require virtualized builders' "
+            "above).",
+            view.widget_errors.get('enabled_restricted_families'))
 
 
 class TestDistroReassignView(TestCaseWithFactory):
@@ -147,7 +317,7 @@
             'field.existing': 'existing',
             'field.actions.change': 'Change',
             }
-        view = create_initialized_view(
+        create_initialized_view(
             distribution, '+reassign', principal=admin,
             method='POST', form=reassign_form)
         self.assertEqual(distribution.owner, self.simple_user)

=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py	2011-08-17 13:02:26 +0000
+++ lib/lp/soyuz/browser/archive.py	2011-09-05 17:01:29 +0000
@@ -22,6 +22,7 @@
     'ArchivePackagesView',
     'ArchiveView',
     'ArchiveViewBase',
+    'EnableRestrictedFamiliesMixin',
     'make_archive_vocabulary',
     'PackageCopyingMixin',
     'traverse_named_ppa',
@@ -2033,7 +2034,38 @@
         return 'Edit %s' % self.context.displayname
 
 
-class ArchiveAdminView(BaseArchiveEditView):
+class EnableRestrictedFamiliesMixin:
+    """A mixin that provides enabled_restricted_families field support"""
+
+    def createEnabledRestrictedFamilies(self, description=None):
+        """Creates the 'enabled_restricted_families' field.
+
+        """
+        terms = []
+        for family in getUtility(IProcessorFamilySet).getRestricted():
+            terms.append(SimpleTerm(
+                family, token=family.name, title=family.title))
+        old_field = IArchive['enabled_restricted_families']
+        return form.Fields(
+            List(__name__=old_field.__name__,
+                 title=old_field.title,
+                 value_type=Choice(vocabulary=SimpleVocabulary(terms)),
+                 required=False,
+                 description=old_field.description if description is None
+                     else description),
+                 render_context=self.render_context)
+
+    def validate_enabled_restricted_families(self, data, error_msg):
+        enabled_restricted_families = data['enabled_restricted_families']
+        require_virtualized = data.get('require_virtualized', False)
+        proc_family_set = getUtility(IProcessorFamilySet)
+        if (not require_virtualized and
+            set(enabled_restricted_families) !=
+                set(proc_family_set.getRestricted())):
+            self.setFieldError('enabled_restricted_families', error_msg)
+
+
+class ArchiveAdminView(BaseArchiveEditView, EnableRestrictedFamiliesMixin):
 
     field_names = ['enabled', 'private', 'commercial', 'require_virtualized',
                    'build_debug_symbols', 'buildd_secret', 'authorized_size',
@@ -2097,6 +2129,16 @@
                 'commercial',
                 'Can only set commericial for private archives.')
 
+        enabled_restricted_families = data.get('enabled_restricted_families')
+        if (enabled_restricted_families and
+            not self.context.canSetEnabledRestrictedFamilies(
+                enabled_restricted_families)):
+            self.setFieldError(
+                'enabled_restricted_families',
+                'Main archives can not be restricted to certain '
+                'architectures unless they are set to build on '
+                'virtualized builders.')
+
     @property
     def owner_is_private_team(self):
         """Is the owner a private team?
@@ -2106,6 +2148,13 @@
         """
         return self.context.owner.visibility == PersonVisibility.PRIVATE
 
+    @property
+    def initial_values(self):
+        return {
+            'enabled_restricted_families':
+                self.context.enabled_restricted_families,
+            }
+
     def setUpFields(self):
         """Override `LaunchpadEditFormView`.
 
@@ -2114,23 +2163,6 @@
         super(ArchiveAdminView, self).setUpFields()
         self.form_fields += self.createEnabledRestrictedFamilies()
 
-    def createEnabledRestrictedFamilies(self):
-        """Creates the 'enabled_restricted_families' field.
-
-        """
-        terms = []
-        for family in getUtility(IProcessorFamilySet).getRestricted():
-            terms.append(SimpleTerm(
-                family, token=family.name, title=family.title))
-        old_field = IArchive['enabled_restricted_families']
-        return form.Fields(
-            List(__name__=old_field.__name__,
-                 title=old_field.title,
-                 value_type=Choice(vocabulary=SimpleVocabulary(terms)),
-                 required=False,
-                 description=old_field.description),
-                 render_context=self.render_context)
-
 
 class ArchiveDeleteView(LaunchpadFormView):
     """View class for deleting `IArchive`s"""

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2011-08-25 11:29:29 +0000
+++ lib/lp/soyuz/model/archive.py	2011-09-05 17:01:29 +0000
@@ -1910,8 +1910,8 @@
         """Retrieve the restricted architecture families this archive can
         build on."""
         # Main archives are always allowed to build on restricted
-        # architectures.
-        if self.is_main:
+        # architectures if require_virtualized is False.
+        if self.is_main and not self.require_virtualized:
             return getUtility(IProcessorFamilySet).getRestricted()
         archive_arch_set = getUtility(IArchiveArchSet)
         restricted_families = archive_arch_set.getRestrictedFamilies(self)
@@ -1921,13 +1921,16 @@
     def _setEnabledRestrictedFamilies(self, value):
         """Set the restricted architecture families this archive can
         build on."""
-        # Main archives are always allowed to build on restricted
-        # architectures.
-        if self.is_main:
+        # Main archives are not allowed to build on restricted
+        # architectures unless they are set to build on virtualized
+        # builders.
+        if (self.is_main and not self.require_virtualized):
             proc_family_set = getUtility(IProcessorFamilySet)
             if set(value) != set(proc_family_set.getRestricted()):
-                raise CannotRestrictArchitectures("Main archives can not "
-                        "be restricted to certain architectures")
+                raise CannotRestrictArchitectures(
+                    "Main archives can not be restricted to certain "
+                    "architectures unless they are set to build on "
+                    "virtualized builders")
         archive_arch_set = getUtility(IArchiveArchSet)
         restricted_families = archive_arch_set.getRestrictedFamilies(self)
         for (family, archive_arch) in restricted_families:

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2011-08-29 19:28:47 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2011-09-05 17:01:29 +0000
@@ -981,14 +981,17 @@
 
     def test_main_archive_can_use_restricted(self):
         # Main archives for distributions can always use restricted
-        # architectures.
+        # architectures if they are not using virtual builders.
         distro = self.factory.makeDistribution()
+        distro.main_archive.require_virtualized = False
         self.assertContentEqual([self.arm],
             distro.main_archive.enabled_restricted_families)
 
-    def test_main_archive_can_not_be_restricted(self):
-        # A main archive can not be restricted to certain architectures.
+    def test_main_archive_can_not_be_restricted_not_virtualized(self):
+        # A main archive can not be restricted to certain architectures
+        # (unless it's set to build on virtualized builders).
         distro = self.factory.makeDistribution()
+        distro.main_archive.require_virtualized = False
         # Restricting to all restricted architectures is fine
         distro.main_archive.enabled_restricted_families = [self.arm]
 
@@ -997,6 +1000,16 @@
 
         self.assertRaises(CannotRestrictArchitectures, restrict)
 
+    def test_main_virtualized_archive_can_be_restricted(self):
+        # A main archive can be restricted to certain architectures
+        # if it's set to build on virtualized builders.
+        distro = self.factory.makeDistribution()
+        distro.main_archive.require_virtualized = True
+
+        # Restricting to architectures is fine.
+        distro.main_archive.enabled_restricted_families = [self.arm]
+        distro.main_archive.enabled_restricted_families = []
+
     def test_default(self):
         """By default, ARM builds are not allowed as ARM is restricted."""
         self.assertEquals(0,