mirror: use apt->mirror-selection instead of relying on version
We used to rely on a version key under apt autoinstall section to specify if we want the old implementation (i.e., a single candidate primary mirror) or the new implementation (i.e., multiple primary mirror candidates). Instead, we now introduce the apt->mirror-selection autoinstall section, and move the primary section under it. If the primary section if under apt, we want the old implementation. If the primary section is under apt->mirror-selection, we want the new implementation. Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
parent
093019b4ca
commit
ff46c48d60
|
@ -307,15 +307,45 @@
|
||||||
"apt": {
|
"apt": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"preserve_sources_list": {
|
"preserve_sources_list": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"primary": {
|
"primary": {
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
"mirror-selection": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"primary": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"const": "country-mirror"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"uri": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"arches": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"uri"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"geoip": {
|
"geoip": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,7 @@ network:
|
||||||
dhcp6: yes
|
dhcp6: yes
|
||||||
debconf-selections: eek
|
debconf-selections: eek
|
||||||
apt:
|
apt:
|
||||||
version: 2
|
mirror-selection:
|
||||||
primary:
|
primary:
|
||||||
- uri: http://mymirror.local/repository/Apt/ubuntu/
|
- uri: http://mymirror.local/repository/Apt/ubuntu/
|
||||||
- country-mirror
|
- country-mirror
|
||||||
|
|
|
@ -50,18 +50,21 @@ elected
|
||||||
|
|
||||||
primary section
|
primary section
|
||||||
---------------
|
---------------
|
||||||
* the "primary section" is what corresponds to the whole 'apt->primary'
|
* the "primary section" contains the different candidates for mirror
|
||||||
autoinstall section. Today we support two formats for this
|
selection. Today we support two different formats for this section, and the
|
||||||
section:
|
position of the primary section is what determines which format is used.
|
||||||
* the legacy format, inherited from curtin, where the whole section denotes
|
* the legacy format, inherited from curtin, where the primary section is a
|
||||||
a single primary candidate. This format cannot be used to specify multiple
|
direct child of the 'apt' section. In this format, the whole section denotes
|
||||||
|
a single primary candidate so it cannot be used to specify multiple
|
||||||
candidates.
|
candidates.
|
||||||
* the more modern format where the primary section is split into multiple
|
* the more modern format where the primary section is a child of the
|
||||||
"entries", each denoting a primary candidate.
|
'mirror-selection' key (which itself is a child of 'apt'). In this format,
|
||||||
|
the section is split into multiple "entries", each denoting a primary
|
||||||
|
candidate.
|
||||||
|
|
||||||
primary entry
|
primary entry
|
||||||
-------------
|
-------------
|
||||||
* represents a fragment of the 'apt->primary' autoinstall section. Each entry
|
* represents a fragment of the 'primary' autoinstall section. Each entry
|
||||||
can be used as a primary candidate.
|
can be used as a primary candidate.
|
||||||
* in the legacy format, the primary entry corresponds to the whole primary
|
* in the legacy format, the primary entry corresponds to the whole primary
|
||||||
section.
|
section.
|
||||||
|
@ -112,7 +115,7 @@ DEFAULT = {
|
||||||
|
|
||||||
@attr.s(auto_attribs=True)
|
@attr.s(auto_attribs=True)
|
||||||
class BasePrimaryEntry(abc.ABC):
|
class BasePrimaryEntry(abc.ABC):
|
||||||
""" Base class to represent an entry from the 'apt->primary' autoinstall
|
""" Base class to represent an entry from the 'primary' autoinstall
|
||||||
section. A BasePrimaryEntry is expected to have a URI and therefore can be
|
section. A BasePrimaryEntry is expected to have a URI and therefore can be
|
||||||
used as a primary candidate. """
|
used as a primary candidate. """
|
||||||
parent: "MirrorModel" = attr.ib(kw_only=True)
|
parent: "MirrorModel" = attr.ib(kw_only=True)
|
||||||
|
@ -131,8 +134,8 @@ class BasePrimaryEntry(abc.ABC):
|
||||||
@attr.s(auto_attribs=True)
|
@attr.s(auto_attribs=True)
|
||||||
class PrimaryEntry(BasePrimaryEntry):
|
class PrimaryEntry(BasePrimaryEntry):
|
||||||
""" Represents a single primary mirror candidate; which can be converted
|
""" Represents a single primary mirror candidate; which can be converted
|
||||||
to/from an entry of the 'apt->primary' autoinstall section in the modern
|
to/from an entry of the 'apt->mirror-selection->primary' autoinstall
|
||||||
format. """
|
section. """
|
||||||
# Having uri set to None is only valid for a country mirror.
|
# Having uri set to None is only valid for a country mirror.
|
||||||
uri: Optional[str] = None
|
uri: Optional[str] = None
|
||||||
# When arches is None, it is assumed that the mirror is compatible with the
|
# When arches is None, it is assumed that the mirror is compatible with the
|
||||||
|
@ -247,21 +250,27 @@ class MirrorModel(object):
|
||||||
return self._default_primary_entries()
|
return self._default_primary_entries()
|
||||||
|
|
||||||
def load_autoinstall_data(self, data):
|
def load_autoinstall_data(self, data):
|
||||||
self.legacy_primary = data.pop("version", 1) < 2
|
|
||||||
if "disable_components" in data:
|
if "disable_components" in data:
|
||||||
self.disabled_components = set(data.pop("disable_components"))
|
self.disabled_components = set(data.pop("disable_components"))
|
||||||
|
|
||||||
|
if "primary" in data and "mirror-selection" in data:
|
||||||
|
raise ValueError("apt->primary and apt->mirror-selection are"
|
||||||
|
" mutually exclusive.")
|
||||||
|
self.legacy_primary = "primary" in data
|
||||||
|
|
||||||
|
primary_candidates = self.get_default_primary_candidates()
|
||||||
if "primary" in data:
|
if "primary" in data:
|
||||||
if self.legacy_primary:
|
|
||||||
# Legacy sections only support a single candidate
|
# Legacy sections only support a single candidate
|
||||||
self.primary_candidates = \
|
primary_candidates = \
|
||||||
[LegacyPrimaryEntry(data.pop("primary"), parent=self)]
|
[LegacyPrimaryEntry(data.pop("primary"), parent=self)]
|
||||||
else:
|
if "mirror-selection" in data:
|
||||||
self.primary_candidates = []
|
mirror_selection = data.pop("mirror-selection")
|
||||||
for section in data.pop("primary"):
|
if "primary" in mirror_selection:
|
||||||
|
primary_candidates = []
|
||||||
|
for section in mirror_selection["primary"]:
|
||||||
entry = PrimaryEntry.from_config(section, parent=self)
|
entry = PrimaryEntry.from_config(section, parent=self)
|
||||||
self.primary_candidates.append(entry)
|
primary_candidates.append(entry)
|
||||||
else:
|
self.primary_candidates = primary_candidates
|
||||||
self.primary_candidates = self.get_default_primary_candidates()
|
|
||||||
|
|
||||||
merge_config(self.config, data)
|
merge_config(self.config, data)
|
||||||
|
|
||||||
|
@ -368,7 +377,6 @@ class MirrorModel(object):
|
||||||
|
|
||||||
def make_autoinstall(self):
|
def make_autoinstall(self):
|
||||||
config = self._get_apt_config_common()
|
config = self._get_apt_config_common()
|
||||||
config["version"] = 1 if self.legacy_primary else 2
|
|
||||||
if self.legacy_primary:
|
if self.legacy_primary:
|
||||||
# Only one candidate is supported
|
# Only one candidate is supported
|
||||||
if self.primary_elected is not None:
|
if self.primary_elected is not None:
|
||||||
|
@ -378,7 +386,7 @@ class MirrorModel(object):
|
||||||
to_serialize = self.primary_candidates[0]
|
to_serialize = self.primary_candidates[0]
|
||||||
config["primary"] = to_serialize.serialize_for_ai()
|
config["primary"] = to_serialize.serialize_for_ai()
|
||||||
else:
|
else:
|
||||||
config["primary"] = \
|
primary = [c.serialize_for_ai() for c in self.primary_candidates]
|
||||||
[c.serialize_for_ai() for c in self.primary_candidates]
|
config["mirror-selection"] = {"primary": primary}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
|
@ -179,32 +179,22 @@ class TestMirrorModel(unittest.TestCase):
|
||||||
model = MirrorModel()
|
model = MirrorModel()
|
||||||
data = {'disable_components': ['non-free']}
|
data = {'disable_components': ['non-free']}
|
||||||
model.load_autoinstall_data(data)
|
model.load_autoinstall_data(data)
|
||||||
self.assertTrue(model.legacy_primary)
|
self.assertFalse(model.legacy_primary)
|
||||||
model.primary_candidates[0].stage()
|
model.primary_candidates[0].stage()
|
||||||
self.assertEqual(set(['non-free']), model.disabled_components)
|
self.assertEqual(set(['non-free']), model.disabled_components)
|
||||||
|
|
||||||
model = MirrorModel()
|
|
||||||
data = {"version": 1}
|
|
||||||
model.load_autoinstall_data(data)
|
|
||||||
self.assertTrue(model.legacy_primary)
|
|
||||||
self.assertEqual(model.primary_candidates,
|
|
||||||
[LegacyPrimaryEntry.new_from_default(parent=model)])
|
|
||||||
|
|
||||||
data = {"version": 2}
|
|
||||||
model.load_autoinstall_data(data)
|
|
||||||
self.assertFalse(model.legacy_primary)
|
|
||||||
self.assertEqual(model.primary_candidates,
|
self.assertEqual(model.primary_candidates,
|
||||||
model._default_primary_entries())
|
model._default_primary_entries())
|
||||||
|
|
||||||
def test_from_autoinstall_modern(self):
|
def test_from_autoinstall_modern(self):
|
||||||
data = {
|
data = {
|
||||||
"version": 2,
|
"mirror-selection": {
|
||||||
"primary": [
|
"primary": [
|
||||||
"country-mirror",
|
"country-mirror",
|
||||||
{
|
{
|
||||||
"uri": "http://mirror",
|
"uri": "http://mirror",
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
model = MirrorModel()
|
model = MirrorModel()
|
||||||
model.load_autoinstall_data(data)
|
model.load_autoinstall_data(data)
|
||||||
|
@ -256,8 +246,7 @@ class TestMirrorModel(unittest.TestCase):
|
||||||
]
|
]
|
||||||
cfg = self.model.make_autoinstall()
|
cfg = self.model.make_autoinstall()
|
||||||
self.assertEqual(cfg["disable_components"], ["non-free"])
|
self.assertEqual(cfg["disable_components"], ["non-free"])
|
||||||
self.assertEqual(cfg["primary"], expected_primary)
|
self.assertEqual(cfg["mirror-selection"]["primary"], expected_primary)
|
||||||
self.assertEqual(cfg["version"], 2)
|
|
||||||
|
|
||||||
def test_make_autoinstall_legacy_primary(self):
|
def test_make_autoinstall_legacy_primary(self):
|
||||||
primary = [{"arches": "amd64", "uri": "http://mirror"}]
|
primary = [{"arches": "amd64", "uri": "http://mirror"}]
|
||||||
|
@ -269,7 +258,6 @@ class TestMirrorModel(unittest.TestCase):
|
||||||
cfg = self.model.make_autoinstall()
|
cfg = self.model.make_autoinstall()
|
||||||
self.assertEqual(cfg["disable_components"], ["non-free"])
|
self.assertEqual(cfg["disable_components"], ["non-free"])
|
||||||
self.assertEqual(cfg["primary"], primary)
|
self.assertEqual(cfg["primary"], primary)
|
||||||
self.assertEqual(cfg["version"], 1)
|
|
||||||
|
|
||||||
def test_create_primary_candidate(self):
|
def test_create_primary_candidate(self):
|
||||||
self.model.legacy_primary = False
|
self.model.legacy_primary = False
|
||||||
|
|
|
@ -62,9 +62,34 @@ class MirrorController(SubiquityController):
|
||||||
autoinstall_schema = { # This is obviously incomplete.
|
autoinstall_schema = { # This is obviously incomplete.
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'version': {'type': 'integer'},
|
|
||||||
'preserve_sources_list': {'type': 'boolean'},
|
'preserve_sources_list': {'type': 'boolean'},
|
||||||
'primary': {'type': 'array'},
|
'primary': {'type': 'array'}, # Legacy format defined by curtin.
|
||||||
|
'mirror-selection': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'primary': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'anyOf': [
|
||||||
|
{
|
||||||
|
'type': 'string',
|
||||||
|
'const': 'country-mirror',
|
||||||
|
}, {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'uri': {'type': 'string'},
|
||||||
|
'arches': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {'type': 'string'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'required': ['uri'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
'geoip': {'type': 'boolean'},
|
'geoip': {'type': 'boolean'},
|
||||||
'sources': {'type': 'object'},
|
'sources': {'type': 'object'},
|
||||||
'disable_components': {
|
'disable_components': {
|
||||||
|
|
|
@ -57,8 +57,21 @@ class TestMirrorController(unittest.IsolatedAsyncioTestCase):
|
||||||
self.controller.model.primary_candidates[0].elect()
|
self.controller.model.primary_candidates[0].elect()
|
||||||
config = self.controller.make_autoinstall()
|
config = self.controller.make_autoinstall()
|
||||||
self.assertIn("disable_components", config.keys())
|
self.assertIn("disable_components", config.keys())
|
||||||
|
self.assertIn("mirror-selection", config.keys())
|
||||||
|
self.assertIn("geoip", config.keys())
|
||||||
|
self.assertNotIn("primary", config.keys())
|
||||||
|
|
||||||
|
def test_make_autoinstall_legacy(self):
|
||||||
|
self.controller.model = MirrorModel()
|
||||||
|
self.controller.model.legacy_primary = True
|
||||||
|
self.controller.model.primary_candidates = \
|
||||||
|
self.controller.model.get_default_primary_candidates()
|
||||||
|
self.controller.model.primary_candidates[0].elect()
|
||||||
|
config = self.controller.make_autoinstall()
|
||||||
|
self.assertIn("disable_components", config.keys())
|
||||||
self.assertIn("primary", config.keys())
|
self.assertIn("primary", config.keys())
|
||||||
self.assertIn("geoip", config.keys())
|
self.assertIn("geoip", config.keys())
|
||||||
|
self.assertNotIn("mirror-selection", config.keys())
|
||||||
|
|
||||||
async def test_run_mirror_testing(self):
|
async def test_run_mirror_testing(self):
|
||||||
def fake_mirror_check_success(output):
|
def fake_mirror_check_success(output):
|
||||||
|
|
Loading…
Reference in New Issue