diff -Nru maas-3.2.9-12055-g.c3d5597a7/debian/changelog maas-3.2.10-12065-g.0093bc7ec/debian/changelog --- maas-3.2.9-12055-g.c3d5597a7/debian/changelog 2023-07-28 01:29:11.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/debian/changelog 2024-01-19 14:24:49.000000000 +0000 @@ -1,8 +1,14 @@ -maas (1:3.2.9-12055-g.c3d5597a7-0ubuntu1~20.04.1) focal; urgency=medium +maas (1:3.2.10-12065-g.0093bc7ec-0ubuntu1~20.04.1) focal; urgency=medium + + * New upstream release, MAAS 3.2.10. + + -- Anton Troyanov Fri, 19 Jan 2024 14:24:49 +0000 + +maas (1:3.2.9-0ubuntu1) focal; urgency=medium * New upstream release, MAAS 3.2.9. - -- Alexsander Silva de Souza Fri, 28 Jul 2023 01:29:11 +0000 + -- Alexsander Silva de Souza Fri, 28 Jul 2023 00:57:30 +0000 maas (1:3.2.8-0ubuntu1) focal; urgency=medium diff -Nru maas-3.2.9-12055-g.c3d5597a7/setup.cfg maas-3.2.10-12065-g.0093bc7ec/setup.cfg --- maas-3.2.9-12055-g.c3d5597a7/setup.cfg 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/setup.cfg 2024-01-19 14:02:15.000000000 +0000 @@ -1,6 +1,6 @@ [metadata] name = maas -version = 3.2.9 +version = 3.2.10 description = Metal As A Service long_description = file: README.rst url = https://maas.io/ @@ -86,6 +86,7 @@ setup.py src/apiclient src/maascli + src/maasperf src/maasserver src/maastesting src/metadataserver diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasperf/tests/maasserver/api/test_machines.py maas-3.2.10-12065-g.0093bc7ec/src/maasperf/tests/maasserver/api/test_machines.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasperf/tests/maasserver/api/test_machines.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasperf/tests/maasserver/api/test_machines.py 2024-01-19 14:02:15.000000000 +0000 @@ -2,20 +2,44 @@ # GNU Affero General Public License version 3 (see the file LICENSE). from django.urls import reverse +from piston3.emitters import Emitter +from piston3.handler import typemapper from maasserver.api.machines import MachinesHandler from maastesting.http import make_HttpRequest from maastesting.perftest import perf_test -@perf_test() -def test_perf_list_machines_MachineHandler_api_endpoint(api_client): - api_client.get(reverse("machines_handler")) +class DummyEmitter(Emitter): + def render(self, request): + self.construct() @perf_test(db_only=True) -def test_perf_list_machines_MachinesHander_direct_call(maas_user): +def test_perf_list_machines_MachineHandler_api_endpoint(admin_api_client): + admin_api_client.get(reverse("machines_handler")) + + +@perf_test(db_only=True) +def test_perf_list_machines_MachinesHander_direct_call(admin): + handler = MachinesHandler() + request = make_HttpRequest() + request.user = admin + + emitter = DummyEmitter( + handler.read(request), + typemapper, + handler, + handler.fields, + anonymous=False, + ) + emitter.render(request) + + +@perf_test(db_only=True) +def test_perf_list_machines_MachinesHander_only_objects(admin): handler = MachinesHandler() request = make_HttpRequest() - request.user = maas_user - handler.read(request) + request.user = admin + + list(handler.read(request)) diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/forms/parameters.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/forms/parameters.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/forms/parameters.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/forms/parameters.py 2024-01-19 14:02:15.000000000 +0000 @@ -26,7 +26,7 @@ "maas_auto_ipmi_user", "maas_auto_ipmi_user_privilege_level", "maas_auto_ipmi_k_g_bmc_key", - "maas_auto_ipmi_cipher_suit_id", + "maas_auto_ipmi_cipher_suite_id", } diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/forms/tests/test_parameters.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/forms/tests/test_parameters.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/forms/tests/test_parameters.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/forms/tests/test_parameters.py 2024-01-19 14:02:15.000000000 +0000 @@ -4,8 +4,12 @@ import random from maasserver.enum import INTERFACE_TYPE, IPADDRESS_TYPE, NODE_STATUS -from maasserver.forms.parameters import ParametersForm +from maasserver.forms.parameters import ( + DEFAULTS_FROM_MAAS_CONFIG, + ParametersForm, +) from maasserver.models import Config +from maasserver.models.config import get_default_config from maasserver.testing.factory import factory from maasserver.testing.testcase import MAASServerTestCase from provisioningserver.drivers.power.ipmi import IPMI_PRIVILEGE_LEVEL_CHOICES @@ -1329,3 +1333,7 @@ }, form.errors, ) + + def test_default_config_keys_exist(self): + defaults_all = get_default_config().keys() + self.assertGreaterEqual(defaults_all, DEFAULTS_FROM_MAAS_CONFIG) diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/models/staticipaddress.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/models/staticipaddress.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/models/staticipaddress.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/models/staticipaddress.py 2024-01-19 14:02:15.000000000 +0000 @@ -659,7 +659,8 @@ ELSE staticip.alloc_type END, interface.id, - inet 'fc00::/7' >> ip /* ULA after non-ULA */ + inet 'fc00::/7' >> ip /* ULA after non-ULA */, + staticip.id """ iface_sql_query = ( """ diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/models/tests/test_staticipaddress.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/models/tests/test_staticipaddress.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/models/tests/test_staticipaddress.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/models/tests/test_staticipaddress.py 2024-01-19 14:02:15.000000000 +0000 @@ -446,6 +446,35 @@ }, ) + def test_get_hostname_ip_mapping_ip_order(self): + domain = Domain.objects.get_default_domain() + node = factory.make_Node() + interface1 = factory.make_Interface(node_config=node.current_config) + subnet = factory.make_Subnet() + staticip1 = factory.make_StaticIPAddress( + alloc_type=IPADDRESS_TYPE.STICKY, + ip=factory.pick_ip_in_Subnet(subnet), + subnet=subnet, + interface=interface1, + ) + staticip2 = factory.make_StaticIPAddress( + alloc_type=IPADDRESS_TYPE.STICKY, + ip=factory.pick_ip_in_Subnet(subnet), + subnet=subnet, + interface=interface1, + ) + self.assertEqual( + dict(StaticIPAddress.objects.get_hostname_ip_mapping(subnet)), + { + f"{node.hostname}.{domain.name}": HostnameIPMapping( + node.system_id, 30, {staticip1.ip}, node.node_type + ), + f"{interface1.name}.{node.hostname}.{domain.name}": HostnameIPMapping( + node.system_id, 30, {staticip2.ip}, node.node_type + ), + }, + ) + def test_get_hostname_ip_mapping_returns_fqdn_and_other(self): hostname = factory.make_name("hostname") domainname = factory.make_name("domain") diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/tests/test_dhcp.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/tests/test_dhcp.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/tests/test_dhcp.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/tests/test_dhcp.py 2024-01-19 14:02:15.000000000 +0000 @@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError from netaddr import IPAddress, IPNetwork +import pytest from testtools import ExpectedException from testtools.matchers import ( AllMatch, @@ -35,6 +36,7 @@ Service, VersionedTextFile, ) +from maasserver.rpc import getClientFor from maasserver.rpc.testing.fixtures import MockLiveRegionToClusterRPCFixture from maasserver.testing.eventloop import ( RegionEventLoopFixture, @@ -51,6 +53,7 @@ from maastesting.djangotestcase import count_queries from maastesting.matchers import MockCalledOnceWith, MockNotCalled from maastesting.twisted import always_fail_with, always_succeed_with +from provisioningserver.rpc import exceptions from provisioningserver.rpc.cluster import ( ConfigureDHCPv4, ConfigureDHCPv6, @@ -2830,6 +2833,36 @@ @wait_for_reactor @inlineCallbacks + def test_closed_handler_drops_connection(self): + # ... when DHCP_CONNECT is True. + self.patch(dhcp.settings, "DHCP_CONNECT", True) + + rack_controller, config = yield deferToDatabase( + self.create_rack_controller + ) + protocol, ipv4_stub, ipv6_stub = yield deferToThread( + self.prepare_rpc, rack_controller + ) + + # Get the client and simulate a closed handler + client = yield getClientFor(rack_controller.system_id) + self.patch( + client._conn, "_sendBoxCommand" + ).side_effect = always_fail_with(RuntimeError("the handler is closed")) + + ipv4_stub.side_effect = always_succeed_with({}) + ipv6_stub.side_effect = always_succeed_with({}) + + # The RuntimeError is propagated + with pytest.raises(RuntimeError): + yield dhcp.configure_dhcp(rack_controller) + + # But the connection should have been closed and no connections should be available now. + with pytest.raises(exceptions.NoConnectionsAvailable): + yield getClientFor(rack_controller.system_id) + + @wait_for_reactor + @inlineCallbacks def test_doesnt_call_configure_for_both_ipv4_and_ipv6(self): # ... when DHCP_CONNECT is False. self.patch(dhcp.settings, "DHCP_CONNECT", False) diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/utils/tests/test_views.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/utils/tests/test_views.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/utils/tests/test_views.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/utils/tests/test_views.py 2024-01-19 14:02:15.000000000 +0000 @@ -382,6 +382,7 @@ MockCallsMatch(call(request), call(request), call(request)), ) self.assertThat(response, IsInstance(HttpResponseConflict)) + self.expectThat(response["Retry-After"], Equals("5")) self.expectThat(response.status_code, Equals(http.client.CONFLICT)) self.expectThat( response.reason_phrase, diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/maasserver/utils/views.py maas-3.2.10-12065-g.0093bc7ec/src/maasserver/utils/views.py --- maas-3.2.9-12055-g.c3d5597a7/src/maasserver/utils/views.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/maasserver/utils/views.py 2024-01-19 14:02:15.000000000 +0000 @@ -38,6 +38,8 @@ logger = logging.getLogger(__name__) +RETRY_AFTER_CONFLICT = 5 + class InternalErrorResponse(BaseException): """Exception raised to exit the transaction context. @@ -149,6 +151,9 @@ def __init__(self, response=None, exc_info=None): super().__init__(response=response) + # Responses with status code 409 should be retried by the clients + # see https://bugs.launchpad.net/maas/+bug/2034014/comments/6 for more information + self["Retry-After"] = RETRY_AFTER_CONFLICT self.exc_info = exc_info diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py maas-3.2.10-12065-g.0093bc7ec/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py --- maas-3.2.9-12055-g.c3d5597a7/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py 2024-01-19 14:02:15.000000000 +0000 @@ -46,6 +46,10 @@ # - [OPERATOR, Operator] # - [ADMIN, Administrator] # argument_format: --ipmi-privilege-level={input} +# maas_auto_ipmi_cipher_suite_id: +# type: string +# max: 2 +# argument_format: --ipmi-cipher-suite-id={input} # packages: # apt: # - freeipmi-tools diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/provisioningserver/rpc/common.py maas-3.2.10-12065-g.0093bc7ec/src/provisioningserver/rpc/common.py --- maas-3.2.9-12055-g.c3d5597a7/src/provisioningserver/rpc/common.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/provisioningserver/rpc/common.py 2024-01-19 14:02:15.000000000 +0000 @@ -2,8 +2,6 @@ # GNU Affero General Public License version 3 (see the file LICENSE). """Common RPC classes and utilties.""" - - from os import getpid from socket import gethostname @@ -129,6 +127,23 @@ """Return labels for the rack/region call latency metric.""" return {"call": cmd.__name__} + def _global_intercept_errback(self, failure): + """Intercept exceptions for every call and take actions.""" + # Due to https://bugs.launchpad.net/maas/+bug/2029417 it might be that a connection is actually + # closed, but we still try to use it. In such case, we close the connection here as soon as we detect it. + if ( + failure.check(RuntimeError) + and "the handler is closed" in failure.getErrorMessage() + ): + log.err( + f"Closed handler detected! Dropping the connection '{self.ident}' and forwarding the exception." + ) + if self._conn.transport: + self._conn.transport.loseConnection() + + # re-raise always! + failure.raiseException() + @PROMETHEUS_METRICS.record_call_latency( "maas_rack_region_rpc_call_latency", get_labels=_get_call_latency_metric_labels, @@ -171,11 +186,15 @@ if timeout is undefined: timeout = 120 # 2 minutes if timeout is None or timeout <= 0: - return self._conn.callRemote(cmd, **kwargs) + d = self._conn.callRemote(cmd, **kwargs) + if isinstance(d, Deferred): + d.addErrback(self._global_intercept_errback) + return d else: - return deferWithTimeout( - timeout, self._conn.callRemote, cmd, **kwargs - ) + d = deferWithTimeout(timeout, self._conn.callRemote, cmd, **kwargs) + if isinstance(d, Deferred): + d.addErrback(self._global_intercept_errback) + return d @asynchronous def getHostCertificate(self): @@ -335,15 +354,25 @@ Here we instead log the failure but do *not* disconnect because it's too disruptive to the running of MAAS. + In case of https://bugs.launchpad.net/maas/+bug/2029417, we drop the connection instead. """ - log.err( - failure, - ( - "Unhandled failure during AMP request. This is probably a bug. " - "Please ensure that this error is handled within application " - "code." - ), - ) + if ( + failure.check(RuntimeError) + and "the handler is closed" in failure.getErrorMessage() + ): + super().unhandledError(failure) + log.err( + "The handler is closed and the exception was unhandled. The connection is dropped." + ) + else: + log.err( + failure, + ( + "Unhandled failure during AMP request. This is probably a bug. " + "Please ensure that this error is handled within application " + "code." + ), + ) @Ping.responder def ping(self): diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/provisioningserver/rpc/tests/test_common.py maas-3.2.10-12065-g.0093bc7ec/src/provisioningserver/rpc/tests/test_common.py --- maas-3.2.9-12055-g.c3d5597a7/src/provisioningserver/rpc/tests/test_common.py 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/provisioningserver/rpc/tests/test_common.py 2024-01-19 14:02:15.000000000 +0000 @@ -2,8 +2,6 @@ # GNU Affero General Public License version 3 (see the file LICENSE). """Tests for common RPC code.""" - - import random import re from unittest.mock import ANY, sentinel @@ -13,6 +11,7 @@ from twisted.internet.defer import Deferred from twisted.internet.protocol import connectionDone from twisted.protocols import amp +from twisted.python.failure import Failure from twisted.test.proto_helpers import StringTransport from maastesting.factory import factory @@ -235,6 +234,38 @@ protocol.connectionLost(connectionDone) self.assertThat(protocol.onConnectionLost, IsFiredDeferred()) + def test_unhandled_error_handler_closed(self): + """ + Test the unhandledError method when the handler is closed. + """ + amp_unhandledError = self.patch_autospec(amp.AMP, "unhandledError") + + protocol = common.RPCProtocol() + + fake_failure = Failure( + RuntimeError( + "unable to perform operation on ; the handler is closed" + ) + ) + + protocol.unhandledError(fake_failure) + + amp_unhandledError.assert_called_once() + + def test_unhandled_error_generic(self): + """ + Test the unhandledError method for a generic failure. + """ + amp_unhandledError = self.patch_autospec(amp.AMP, "unhandledError") + + protocol = common.RPCProtocol() + + fake_failure = Failure(Exception("Some generic error")) + + protocol.unhandledError(fake_failure) + + amp_unhandledError.assert_not_called() + class TestRPCProtocol_UnhandledErrorsWhenHandlingResponses(MAASTestCase): diff -Nru maas-3.2.9-12055-g.c3d5597a7/src/provisioningserver/templates/uefi/config.local.amd64.template maas-3.2.10-12065-g.0093bc7ec/src/provisioningserver/templates/uefi/config.local.amd64.template --- maas-3.2.9-12055-g.c3d5597a7/src/provisioningserver/templates/uefi/config.local.amd64.template 2023-07-28 00:57:30.000000000 +0000 +++ maas-3.2.10-12065-g.0093bc7ec/src/provisioningserver/templates/uefi/config.local.amd64.template 2024-01-19 14:02:15.000000000 +0000 @@ -4,17 +4,11 @@ menuentry 'Local' { echo 'Booting local disk...' + # The bootloader paths list for secure boot is shortened because of LP:2022084 for bootloader in \ boot/bootx64.efi \ ubuntu/shimx64.efi \ ubuntu/grubx64.efi \ - centos/shimx64.efi \ - centos/grubx64.efi \ - redhat/shimx64.efi \ - redhat/grubx64.efi \ - rhel/shimx64.efi \ - rhel/grubx64.efi \ - red/grubx64.efi \ Microsoft/Boot/bootmgfw.efi; do search --set=root --file /efi/$bootloader if [ $? -eq 0 ]; then @@ -22,6 +16,27 @@ boot fi done + + if [ "${shim_lock}" != "y" ]; then + echo 'Secure boot is disabled, trying chainloader...' + for bootloader in \ + centos/shimx64.efi \ + centos/grubx64.efi \ + redhat/shimx64.efi \ + redhat/grubx64.efi \ + rhel/shimx64.efi \ + rhel/grubx64.efi \ + suse/shim.efi \ + suse/grubx64.efi \ + red/grubx64.efi; do + search --set=root --file /efi/$bootloader + if [ $? -eq 0 ]; then + chainloader /efi/$bootloader + boot + fi + done + fi + # If no bootloader is found exit and allow the next device to boot. - exit + exit 1 }