Anton Troyanov has proposed merging ~troyanov/maas:migrate-node-power-credentials into maas:master.
Requested reviews: MAAS Maintainers (maas-maintainers) For more details, see: https://code.launchpad.net/~troyanov/maas/+git/maas/+merge/431804 -- Your team MAAS Committers is subscribed to branch maas:master.
diff --git a/src/maasserver/api/nodes.py b/src/maasserver/api/nodes.py index 04c2457..891b335 100644 --- a/src/maasserver/api/nodes.py +++ b/src/maasserver/api/nodes.py @@ -583,7 +583,7 @@ class NodeHandler(OperationsHandler): node = Node.objects.get_node_or_404( system_id, request.user, NodePermission.admin_read ) - return node.power_parameters + return node.get_power_parameters() class AnonNodeHandler(AnonymousOperationsHandler): @@ -1223,5 +1223,6 @@ class PowersMixin: machines = self.base_model.objects.filter(system_id__in=match_ids) return { - machine.system_id: machine.power_parameters for machine in machines + machine.system_id: machine.get_power_parameters() + for machine in machines } diff --git a/src/maasserver/api/pods.py b/src/maasserver/api/pods.py index 58c8910..dc3d2d2 100644 --- a/src/maasserver/api/pods.py +++ b/src/maasserver/api/pods.py @@ -253,7 +253,7 @@ class VmHostHandler(OperationsHandler): This method is reserved for admin users. """ pod = Pod.objects.get_pod_or_404(id, request.user, PodPermission.edit) - return pod.power_parameters + return pod.get_power_parameters() @admin_method @operation(idempotent=False) diff --git a/src/maasserver/api/tests/test_enlistment.py b/src/maasserver/api/tests/test_enlistment.py index 1ac2b30..fe5f429 100644 --- a/src/maasserver/api/tests/test_enlistment.py +++ b/src/maasserver/api/tests/test_enlistment.py @@ -104,7 +104,7 @@ class TestEnlistmentAPI(APITestCase.ForAnonymousAndUserAndAdmin): self.assertEqual(http.client.OK, response.status_code) [machine] = Machine.objects.filter(hostname=hostname) for key, value in power_parameters.items(): - self.assertEqual(machine.bmc.power_parameters[key], value) + self.assertEqual(machine.bmc.get_power_parameters()[key], value) self.assertEqual(power_type, machine.power_type) def test_POST_create_creates_machine_with_arch_only(self): @@ -463,7 +463,7 @@ class TestAnonymousEnlistmentAPI(APITestCase.ForAnonymous): self.assertEqual(hostname, machine.hostname) self.assertEqual(architecture, machine.architecture) for key, value in power_parameters.items(): - self.assertEqual(machine.bmc.power_parameters[key], value) + self.assertEqual(machine.bmc.get_power_parameters()[key], value) self.assertThat(mock_create_machine, MockNotCalled()) self.assertEqual( machine.system_id, json_load_bytes(response.content)["system_id"] @@ -664,7 +664,7 @@ class TestSimpleUserLoggedInEnlistmentAPI(APITestCase.ForUser): "power_id": new_power_id, "power_address": new_power_address, }, - machine.power_parameters, + machine.get_power_parameters(), ) def test_POST_returns_limited_fields(self): @@ -790,7 +790,7 @@ class TestAdminLoggedInEnlistmentAPI(APITestCase.ForAdmin): system_id=json_load_bytes(response.content)["system_id"] ) self.assertEqual("manual", machine.power_type) - self.assertEqual({}, machine.power_parameters) + self.assertEqual({}, machine.get_power_parameters()) def test_POST_sets_power_parameters_field(self): # The api allows the setting of a Machine's power_parameters field. @@ -822,7 +822,7 @@ class TestAdminLoggedInEnlistmentAPI(APITestCase.ForAdmin): "power_pass": new_power_pass, "power_address": new_power_address, }, - reload_object(machine).power_parameters, + reload_object(machine).get_power_parameters(), ) def test_POST_updates_power_parameters_rejects_unknown_param(self): @@ -869,7 +869,7 @@ class TestAdminLoggedInEnlistmentAPI(APITestCase.ForAdmin): system_id=json_load_bytes(response.content)["system_id"] ) self.assertEqual( - {"param": param}, reload_object(machine).power_parameters + {"param": param}, reload_object(machine).get_power_parameters() ) def test_POST_admin_creates_machine_in_commissioning_state(self): diff --git a/src/maasserver/api/tests/test_machine.py b/src/maasserver/api/tests/test_machine.py index c67d4a1..a2bd82c 100644 --- a/src/maasserver/api/tests/test_machine.py +++ b/src/maasserver/api/tests/test_machine.py @@ -2237,7 +2237,7 @@ class TestMachineAPI(APITestCase.ForUser): "power_pass": new_power_pass, "power_address": new_power_address, }, - reload_object(machine).power_parameters, + reload_object(machine).get_power_parameters(), ) def test_PUT_updates_cpu_memory(self): @@ -2277,7 +2277,7 @@ class TestMachineAPI(APITestCase.ForUser): (response.status_code, json_load_bytes(response.content)), ) self.assertEqual( - power_parameters, reload_object(machine).power_parameters + power_parameters, reload_object(machine).get_power_parameters() ) def test_PUT_updates_power_type_default_resets_params(self): @@ -2297,7 +2297,11 @@ class TestMachineAPI(APITestCase.ForUser): machine = reload_object(machine) self.assertEqual( - (http.client.OK, machine.power_type, machine.power_parameters), + ( + http.client.OK, + machine.power_type, + machine.get_power_parameters(), + ), (response.status_code, "", {}), ) @@ -2326,7 +2330,7 @@ class TestMachineAPI(APITestCase.ForUser): (response.status_code, json_load_bytes(response.content)), ) self.assertEqual( - power_parameters, reload_object(machine).power_parameters + power_parameters, reload_object(machine).get_power_parameters() ) def test_PUT_updates_power_type_empty_skip_check_to_force_params(self): @@ -2353,7 +2357,11 @@ class TestMachineAPI(APITestCase.ForUser): machine = reload_object(machine) self.assertEqual( - (http.client.OK, machine.power_type, machine.power_parameters), + ( + http.client.OK, + machine.power_type, + machine.get_power_parameters(), + ), (response.status_code, "", {"param": new_param}), ) @@ -2378,7 +2386,8 @@ class TestMachineAPI(APITestCase.ForUser): self.assertEqual(http.client.OK, response.status_code) self.assertEqual( - {new_param: new_value}, reload_object(machine).power_parameters + {new_param: new_value}, + reload_object(machine).get_power_parameters(), ) def test_PUT_updates_power_parameters_empty_string(self): @@ -2400,7 +2409,7 @@ class TestMachineAPI(APITestCase.ForUser): self.assertEqual(http.client.OK, response.status_code) self.assertEqual( - "", reload_object(machine).power_parameters["power_pass"] + "", reload_object(machine).get_power_parameters()["power_pass"] ) def test_PUT_sets_zone(self): diff --git a/src/maasserver/api/tests/test_machines.py b/src/maasserver/api/tests/test_machines.py index 230ecf8..821a2ce 100644 --- a/src/maasserver/api/tests/test_machines.py +++ b/src/maasserver/api/tests/test_machines.py @@ -204,7 +204,8 @@ class TestMachinesAPI(APITestCase.ForUser): machine = Machine.objects.get(system_id=system_id) self.assertEqual(machine.hostname, hostname) self.assertCountEqual( - workaround_flags, machine.power_parameters["workaround_flags"] + workaround_flags, + machine.get_power_parameters()["workaround_flags"], ) def test_POST_creates_ipmi_machine_sets_mac_addresses_empty_no_arch(self): @@ -237,7 +238,7 @@ class TestMachinesAPI(APITestCase.ForUser): Equals(set()), ) self.assertEqual( - power_address, machine.power_parameters["power_address"] + power_address, machine.get_power_parameters()["power_address"] ) def test_POST_when_logged_in_creates_machine_in_declared_state(self): @@ -323,7 +324,7 @@ class TestMachinesAPI(APITestCase.ForUser): )["system_id"] machine = Machine.objects.get(system_id=system_id) self.assertEqual("", machine.power_type) - self.assertEqual({}, machine.power_parameters) + self.assertEqual({}, machine.get_power_parameters()) def test_POST_handles_error_when_unable_to_access_bmc(self): # Regression test for LP1600328 @@ -346,9 +347,9 @@ class TestMachinesAPI(APITestCase.ForUser): machine = Machine.objects.get(system_id=parsed_result["system_id"]) self.assertEqual("virsh", parsed_result["power_type"]) self.assertEqual( - power_address, machine.power_parameters["power_address"] + power_address, machine.get_power_parameters()["power_address"] ) - self.assertEqual(power_id, machine.power_parameters["power_id"]) + self.assertEqual(power_id, machine.get_power_parameters()["power_id"]) def test_POST_sets_description(self): # Regression test for LP1707562 diff --git a/src/maasserver/api/tests/test_node.py b/src/maasserver/api/tests/test_node.py index c37b11a..63ea132 100644 --- a/src/maasserver/api/tests/test_node.py +++ b/src/maasserver/api/tests/test_node.py @@ -515,7 +515,7 @@ class TestPowerParameters(APITestCase.ForUser): http.client.OK, response.status_code, response.content ) parsed_params = json_load_bytes(response.content) - self.assertEqual(node.power_parameters, parsed_params) + self.assertEqual(node.get_power_parameters(), parsed_params) def test_get_power_parameters_user(self): power_parameters = {factory.make_string(): factory.make_string()} @@ -542,7 +542,7 @@ class TestPowerParameters(APITestCase.ForUser): http.client.OK, response.status_code, response.content ) parsed_params = json_load_bytes(response.content) - self.assertEqual(node.power_parameters, parsed_params) + self.assertEqual(node.get_power_parameters(), parsed_params) def test_get_power_parameters_rbac_pool_user(self): self.patch(auth, "validate_user_external_auth").return_value = True @@ -570,7 +570,7 @@ class TestPowerParameters(APITestCase.ForUser): http.client.OK, response.status_code, response.content ) parsed_params = json_load_bytes(response.content) - self.assertEqual(node.power_parameters, parsed_params) + self.assertEqual(node.get_power_parameters(), parsed_params) def test_get_power_parameters_view_lock(self): self.become_admin() @@ -585,7 +585,7 @@ class TestPowerParameters(APITestCase.ForUser): http.client.OK, response.status_code, response.content ) parsed_params = json_load_bytes(response.content) - self.assertEqual(node.power_parameters, parsed_params) + self.assertEqual(node.get_power_parameters(), parsed_params) class TestSetWorkloadAnnotations(APITestCase.ForUser): diff --git a/src/maasserver/api/tests/test_nodes.py b/src/maasserver/api/tests/test_nodes.py index 88fa7e6..91922f8 100644 --- a/src/maasserver/api/tests/test_nodes.py +++ b/src/maasserver/api/tests/test_nodes.py @@ -792,7 +792,8 @@ class TestPowersMixin(APITestCase.ForUser): ) parsed = json.loads(response.content.decode(settings.DEFAULT_CHARSET)) expected = { - machine.system_id: machine.power_parameters for machine in machines + machine.system_id: machine.get_power_parameters() + for machine in machines } self.assertEqual(expected, parsed) @@ -817,7 +818,7 @@ class TestPowersMixin(APITestCase.ForUser): ) parsed = json.loads(response.content.decode(settings.DEFAULT_CHARSET)) expected = { - machine.system_id: machine.power_parameters + machine.system_id: machine.get_power_parameters() for machine in expected_machines } self.assertEqual(expected, parsed) diff --git a/src/maasserver/api/tests/test_pods.py b/src/maasserver/api/tests/test_pods.py index 4eff05b..24a4f7e 100644 --- a/src/maasserver/api/tests/test_pods.py +++ b/src/maasserver/api/tests/test_pods.py @@ -214,7 +214,7 @@ class TestPodsAPIAdmin(PodAPITestForAdmin, PodMixin): self.assertEqual(http.client.OK, response.status_code) parsed_result = json_load_bytes(response.content) pod = Pod.objects.get(id=parsed_result["id"]) - self.assertEqual(pod.power_parameters["project"], "default") + self.assertEqual(pod.get_power_parameters()["project"], "default") def test_create_creates_pod_with_default_resource_pool(self): self.patch(pods, "post_commit_do") @@ -406,7 +406,7 @@ class TestPodAPIAdmin(PodAPITestForAdmin, PodMixin): self.assertEqual(new_name, pod.name) self.assertEqual(new_pool, pod.pool) self.assertCountEqual(new_tags, pod.tags) - self.assertEqual(new_power_parameters, pod.power_parameters) + self.assertEqual(new_power_parameters, pod.get_power_parameters()) self.assertEqual(new_zone, pod.zone) def test_PUT_updates_discovers_syncs_and_returns_pod(self): @@ -451,7 +451,7 @@ class TestPodAPIAdmin(PodAPITestForAdmin, PodMixin): pod.refresh_from_db() self.assertIsNotNone(Tag.objects.get(name="pod-console-logging")) self.assertEqual(new_name, pod.name) - self.assertEqual(power_parameters, pod.power_parameters) + self.assertEqual(power_parameters, pod.get_power_parameters()) def test_PUT_update_updates_pod_default_macvlan_mode(self): self.patch(pods, "post_commit_do") @@ -481,16 +481,16 @@ class TestPodAPIAdmin(PodAPITestForAdmin, PodMixin): def test_parameters_returns_pod_parameters(self): pod = factory.make_Pod() - pod.power_parameters = { - factory.make_name("key"): factory.make_name("value") - } + pod.set_power_parameters( + {factory.make_name("key"): factory.make_name("value")} + ) pod.save() response = self.client.get(get_pod_uri(pod), {"op": "parameters"}) self.assertEqual( http.client.OK, response.status_code, response.content ) parsed_params = json_load_bytes(response.content) - self.assertEqual(pod.power_parameters, parsed_params) + self.assertEqual(pod.get_power_parameters(), parsed_params) def test_compose_not_allowed_on_none_composable_pod(self): pod = make_pod_with_hints() diff --git a/src/maasserver/clusterrpc/tests/test_driver_parameters.py b/src/maasserver/clusterrpc/tests/test_driver_parameters.py index 100bb5a..a50c281 100644 --- a/src/maasserver/clusterrpc/tests/test_driver_parameters.py +++ b/src/maasserver/clusterrpc/tests/test_driver_parameters.py @@ -277,6 +277,7 @@ class TestMakeSettingField(MAASServerTestCase): "choices": [], "default": "", "scope": "bmc", + "secret": False, } self.assertEqual(expected_field, json_field) @@ -289,6 +290,7 @@ class TestMakeSettingField(MAASServerTestCase): "choices": [["spam", "Spam"], ["eggs", "Eggs"]], "default": "spam", "scope": "bmc", + "secret": False, } json_field = make_setting_field(**expected_field) self.assertEqual(expected_field, json_field) @@ -304,7 +306,7 @@ class TestMakeSettingField(MAASServerTestCase): def test_creates_password_fields(self): json_field = make_setting_field( - "some_field", "Some Label", field_type="password" + "some_field", "Some Label", field_type="password", secret=True ) expected_field = { "name": "some_field", @@ -314,6 +316,7 @@ class TestMakeSettingField(MAASServerTestCase): "choices": [], "default": "", "scope": "bmc", + "secret": True, } self.assertEqual(expected_field, json_field) diff --git a/src/maasserver/clusterrpc/tests/test_pods.py b/src/maasserver/clusterrpc/tests/test_pods.py index 3c42e2b..36badc6 100644 --- a/src/maasserver/clusterrpc/tests/test_pods.py +++ b/src/maasserver/clusterrpc/tests/test_pods.py @@ -356,7 +356,7 @@ class TestSendPodCommissioningResults(MAASServerTestCase): pod.name, pod.power_type, node.system_id, - pod.power_parameters, + pod.get_power_parameters(), token.consumer.key, token.key, token.secret, @@ -371,7 +371,7 @@ class TestSendPodCommissioningResults(MAASServerTestCase): name=pod.name, type=pod.power_type, system_id=node.system_id, - context=pod.power_parameters, + context=pod.get_power_parameters(), consumer_key=token.consumer.key, token_key=token.key, token_secret=token.secret, @@ -395,7 +395,7 @@ class TestSendPodCommissioningResults(MAASServerTestCase): pod.name, pod.power_type, node.system_id, - pod.power_parameters, + pod.get_power_parameters(), token.consumer.key, token.key, token.secret, @@ -423,7 +423,7 @@ class TestSendPodCommissioningResults(MAASServerTestCase): pod.name, pod.power_type, node.system_id, - pod.power_parameters, + pod.get_power_parameters(), token.consumer.key, token.key, token.secret, @@ -453,7 +453,7 @@ class TestSendPodCommissioningResults(MAASServerTestCase): pod.name, pod.power_type, node.system_id, - pod.power_parameters, + pod.get_power_parameters(), token.consumer.key, token.key, token.secret, @@ -483,7 +483,7 @@ class TestSendPodCommissioningResults(MAASServerTestCase): pod.name, pod.power_type, node.system_id, - pod.power_parameters, + pod.get_power_parameters(), token.consumer.key, token.key, token.secret, @@ -505,7 +505,7 @@ class TestComposeMachine(MAASServerTestCase): machine, hints = wait_for_reactor(compose_machine)( client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), sentinel.request, pod.id, pod.name, @@ -516,7 +516,7 @@ class TestComposeMachine(MAASServerTestCase): MockCalledOnceWith( ComposeMachine, type=pod.power_type, - context=pod.power_parameters, + context=pod.get_power_parameters(), request=sentinel.request, pod_id=pod.id, name=pod.name, @@ -535,7 +535,7 @@ class TestComposeMachine(MAASServerTestCase): wait_for_reactor(compose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), sentinel.request, pod.id, pod.name, @@ -556,7 +556,7 @@ class TestComposeMachine(MAASServerTestCase): wait_for_reactor(compose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), sentinel.request, pod.id, pod.name, @@ -578,7 +578,7 @@ class TestComposeMachine(MAASServerTestCase): wait_for_reactor(compose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), sentinel.request, pod.id, pod.name, @@ -599,7 +599,7 @@ class TestComposeMachine(MAASServerTestCase): wait_for_reactor(compose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), sentinel.request, pod.id, pod.name, @@ -622,7 +622,11 @@ class TestDecomposeMachine(MAASServerTestCase): client.return_value = succeed({"hints": hints}) result = wait_for_reactor(decompose_machine)( - client, pod.power_type, pod.power_parameters, pod.id, pod.name + client, + pod.power_type, + pod.get_power_parameters(), + pod.id, + pod.name, ) self.assertThat( @@ -630,7 +634,7 @@ class TestDecomposeMachine(MAASServerTestCase): MockCalledOnceWith( DecomposeMachine, type=pod.power_type, - context=pod.power_parameters, + context=pod.get_power_parameters(), pod_id=pod.id, name=pod.name, ), @@ -647,7 +651,7 @@ class TestDecomposeMachine(MAASServerTestCase): wait_for_reactor(decompose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), pod.id, pod.name, ) @@ -667,7 +671,7 @@ class TestDecomposeMachine(MAASServerTestCase): wait_for_reactor(decompose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), pod.id, pod.name, ) @@ -688,7 +692,7 @@ class TestDecomposeMachine(MAASServerTestCase): wait_for_reactor(decompose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), pod.id, pod.name, ) @@ -708,7 +712,7 @@ class TestDecomposeMachine(MAASServerTestCase): wait_for_reactor(decompose_machine), client, pod.power_type, - pod.power_parameters, + pod.get_power_parameters(), pod.id, pod.name, ) diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py index 93e90af..1a01fea 100644 --- a/src/maasserver/forms/__init__.py +++ b/src/maasserver/forms/__init__.py @@ -286,7 +286,7 @@ class WithPowerTypeMixin: # Integrate the machines existing parameters if unset by form. if machine: - for key, value in machine.power_parameters.items(): + for key, value in machine.get_power_parameters().items(): if parameters.get(key) is None: parameters[key] = value return parameters @@ -319,7 +319,7 @@ class WithPowerTypeMixin: if form.instance is not None: if form.instance.power_type != "": form.initial[type_field_name] = form.instance.power_type - if form.instance.power_parameters != "": + if form.instance.get_power_parameters() != "": for key, value in parameters.items(): form.initial[f"{params_field_name}_{key}"] = value diff --git a/src/maasserver/forms/tests/test_controller.py b/src/maasserver/forms/tests/test_controller.py index 61385f7..ca5ffa5 100644 --- a/src/maasserver/forms/tests/test_controller.py +++ b/src/maasserver/forms/tests/test_controller.py @@ -63,7 +63,7 @@ class TestControllerForm(MAASServerTestCase): ) rack = form.save() self.assertEqual( - power_parameters_field, rack.power_parameters["field"] + power_parameters_field, rack.get_power_parameters()["field"] ) def test_sets_zone(self): diff --git a/src/maasserver/forms/tests/test_machine.py b/src/maasserver/forms/tests/test_machine.py index de20bef..2916c22 100644 --- a/src/maasserver/forms/tests/test_machine.py +++ b/src/maasserver/forms/tests/test_machine.py @@ -519,7 +519,7 @@ class TestAdminMachineForm(MAASServerTestCase): self.assertEqual( (hostname, power_type, {"field": power_parameters_field}), - (node.hostname, node.power_type, node.power_parameters), + (node.hostname, node.power_type, node.get_power_parameters()), ) def test_AdminMachineForm_doesnt_changes_power_parameters(self): @@ -536,7 +536,7 @@ class TestAdminMachineForm(MAASServerTestCase): instance=node, ) node = form.save() - self.assertEqual(power_parameters, node.power_parameters) + self.assertEqual(power_parameters, node.get_power_parameters()) def test_AdminMachineForm_doesnt_change_power_type(self): power_type = factory.pick_power_type() @@ -640,7 +640,7 @@ class TestAdminMachineWithMACAddressForm(MAASServerTestCase): ) self.assertTrue(form.is_valid()) machine = form.save() - power_params = machine.bmc.power_parameters + power_params = machine.bmc.get_power_parameters() self.assertIn("certificate", power_params) self.assertIn("key", power_params) cert = Certificate.from_pem( @@ -648,5 +648,7 @@ class TestAdminMachineWithMACAddressForm(MAASServerTestCase): ) self.assertEqual(cert.cn(), sample_cert.cn()) # cert/key are not per-instance parameters - self.assertNotIn("certificate", machine.instance_power_parameters) - self.assertNotIn("key", machine.instance_power_parameters) + self.assertNotIn( + "certificate", machine.get_instance_power_parameters() + ) + self.assertNotIn("key", machine.get_instance_power_parameters()) diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py index 9a4dd86..beac59b 100644 --- a/src/maasserver/forms/tests/test_pods.py +++ b/src/maasserver/forms/tests/test_pods.py @@ -175,7 +175,8 @@ class TestPodForm(MAASTransactionServerTestCase): pod = form.save() self.assertEqual(pod.power_type, pod_info["type"]) self.assertEqual( - pod.power_parameters["power_address"], pod_info["power_address"] + pod.get_power_parameters()["power_address"], + pod_info["power_address"], ) self.assertEqual(pod.cores, 0) self.assertEqual(pod.memory, 0) @@ -197,10 +198,11 @@ class TestPodForm(MAASTransactionServerTestCase): self.assertTrue(form.is_valid(), form._errors) pod = form.save() self.assertEqual( - pod_info["power_address"], pod.power_parameters["power_address"] + pod_info["power_address"], + pod.get_power_parameters()["power_address"], ) self.assertEqual( - pod_info["power_pass"], pod.power_parameters["power_pass"] + pod_info["power_pass"], pod.get_power_parameters()["power_pass"] ) def test_creates_pod_with_overcommit(self): @@ -346,7 +348,7 @@ class TestPodForm(MAASTransactionServerTestCase): self.assertEqual(cpu_over_commit, pod.cpu_over_commit_ratio) self.assertEqual(memory_over_commit, pod.memory_over_commit_ratio) self.assertEqual(memory_over_commit, pod.memory_over_commit_ratio) - self.assertEqual(power_parameters, pod.power_parameters) + self.assertEqual(power_parameters, pod.get_power_parameters()) def test_updates_existing_pod(self): zone = factory.make_Zone() diff --git a/src/maasserver/migrations/maasserver/0290_migrate_node_power_parameters.py b/src/maasserver/migrations/maasserver/0290_migrate_node_power_parameters.py new file mode 100644 index 0000000..aec854e --- /dev/null +++ b/src/maasserver/migrations/maasserver/0290_migrate_node_power_parameters.py @@ -0,0 +1,63 @@ +# Generated by Django 3.2.12 on 2022-11-15 15:12 + +from datetime import datetime + +from django.db import migrations + +from provisioningserver.drivers.power.registry import PowerDriverRegistry + + +def move_secrets(apps, schema_editor): + BMC = apps.get_model("maasserver", "BMC") + Secret = apps.get_model("maasserver", "Secret") + + now = datetime.utcnow() + + bmc_secrets = {} + + for bmc_id, power_type, power_parameters in BMC.objects.values_list( + "id", "power_type", "power_parameters" + ): + + power_driver = PowerDriverRegistry.get_item(power_type) + if not power_driver: + continue + + parameters = power_parameters.copy() + + for power_parameter in power_parameters.keys(): + setting = power_driver.get_setting(power_parameter) + + if not setting.get("secret"): + continue + + field_name = setting.get("name") + field_value = power_parameters.get(field_name) + + bmc_secrets[bmc_id] = { + **bmc_secrets.get(bmc_id, {}), + **{field_name: field_value}, + } + + power_parameters.pop(field_name, None) + + BMC.objects.filter(id=bmc_id).update(power_parameters=parameters) + + Secret.objects.bulk_create( + Secret( + path=f"bmc/{bmc_id}/power-parameters", + value=secret, + created=now, + updated=now, + ) + for bmc_id, secret in bmc_secrets.items() + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("maasserver", "0289_vault_secret"), + ] + + operations = [migrations.RunPython(move_secrets)] diff --git a/src/maasserver/models/bmc.py b/src/maasserver/models/bmc.py index aab469c..b902288 100644 --- a/src/maasserver/models/bmc.py +++ b/src/maasserver/models/bmc.py @@ -70,7 +70,10 @@ from maasserver.utils.threads import deferToDatabase from metadataserver.enum import RESULT_TYPE from provisioningserver.drivers import SETTING_SCOPE from provisioningserver.drivers.pod import InterfaceAttachType -from provisioningserver.drivers.power.registry import PowerDriverRegistry +from provisioningserver.drivers.power.registry import ( + PowerDriverRegistry, + sanitise_power_parameters, +) from provisioningserver.enum import MACVLAN_MODE_CHOICES from provisioningserver.logger import get_maas_logger from provisioningserver.utils.constraints import LabeledConstraintMap @@ -110,6 +113,47 @@ def get_ip_modes(requested_machine): return ip_modes +def create_bmc(**kwargs): + power_type = kwargs.get("power_type", None) + power_parameters = kwargs.get("power_parameters", {}) + + power_parameters, secrets = sanitise_power_parameters( + power_type, power_parameters + ) + kwargs["power_parameters"] = power_parameters + + bmc = BMC.objects.create(**kwargs) + + from maasserver.secrets import SecretManager + + if secrets: + SecretManager().set_composite_secret( + "power-parameters", secrets, obj=bmc.as_bmc() + ) + return bmc + + +def get_or_create_bmc(**kwargs): + power_type = kwargs.get("power_type", None) + power_parameters = kwargs.get("power_parameters", {}) + + power_parameters, secrets = sanitise_power_parameters( + power_type, power_parameters + ) + kwargs["power_parameters"] = power_parameters + + bmc, created = BMC.objects.get_or_create(**kwargs) + + if created and secrets: + from maasserver.secrets import SecretManager + + SecretManager().set_composite_secret( + "power-parameters", secrets, obj=bmc.as_bmc() + ) + + return bmc, created + + class BaseBMCManager(Manager): """A utility to manage the collection of BMCs.""" @@ -333,6 +377,9 @@ class BMC(CleanSave, TimestampedModel): def delete(self): """Delete this BMC.""" maaslog.info("%s: Deleting BMC", self) + from maasserver.secrets import SecretManager + + SecretManager().delete_all_object_secrets(self) super().delete() def save(self, *args, **kwargs): @@ -356,10 +403,35 @@ class BMC(CleanSave, TimestampedModel): else: break + def get_power_parameters(self): + from maasserver.secrets import SecretManager + + power_parameters = self.power_parameters or {} + return { + **power_parameters, + **SecretManager().get_composite_secret( + "power-parameters", obj=self.as_bmc(), default={} + ), + } + + def set_power_parameters(self, power_parameters): + power_parameters, secrets = sanitise_power_parameters( + self.power_type, power_parameters + ) + from maasserver.secrets import SecretManager + + if secrets: + SecretManager().set_composite_secret( + "power-parameters", secrets, obj=self.as_bmc() + ) + self.power_parameters = power_parameters + def clean(self): """Update our ip_address if the address extracted from our power parameters has changed.""" - new_ip = BMC.extract_ip_address(self.power_type, self.power_parameters) + new_ip = BMC.extract_ip_address( + self.power_type, self.get_power_parameters() + ) current_ip = None if self.ip_address is None else self.ip_address.ip if new_ip != current_ip: if not new_ip: @@ -685,7 +757,7 @@ class Pod(BMC): @property def tracked_project(self) -> str: """Return the project tracked by the Pod, or empty string.""" - return self.power_parameters.get("project", "") + return self.get_power_parameters().get("project", "") @property def host(self): @@ -907,7 +979,9 @@ class Pod(BMC): **kwargs, ) machine.bmc = self - machine.instance_power_parameters = discovered_machine.power_parameters + machine.set_instance_power_parameters( + discovered_machine.power_parameters + ) if not machine.hostname: machine.set_random_hostname() machine.save() @@ -1125,7 +1199,7 @@ class Pod(BMC): # Sync power state and parameters for this machine always. existing_machine.power_state = discovered_machine.power_state - existing_machine.instance_power_parameters = ( + existing_machine.set_instance_power_parameters( discovered_machine.power_parameters ) existing_machine.cpu_count = discovered_machine.cores @@ -1490,7 +1564,7 @@ class Pod(BMC): pool.pool_id: pool for pool in self.storage_pools.all() } possible_default = None - upgrade_default_pool = self.power_parameters.get( + upgrade_default_pool = self.get_power_parameters().get( "default_storage_pool" ) for discovered_pool in discovered_storage_pools: @@ -1524,7 +1598,7 @@ class Pod(BMC): if not self.default_storage_pool and possible_default: self.default_storage_pool = possible_default if upgrade_default_pool is not None: - self.power_parameters = self.power_parameters.copy() + self.power_parameters = self.get_power_parameters().copy() self.power_parameters.pop("default_storage_pool", None) self.save() elif self.default_storage_pool in storage_pools_by_id.values(): @@ -1545,13 +1619,14 @@ class Pod(BMC): interfaces, and/or block devices that do not match the `discovered_pod` values will be removed. """ - if self.power_type == "lxd" and "password" in self.power_parameters: + pod_power_parameters = self.get_power_parameters() + if self.power_type == "lxd" and "password" in pod_power_parameters: # ensure LXD trust_password is removed if it's there # # XXX copy and replace the whole attribute as the CleanSave base # class tracks which attributes to update on save via __setattr__, # so changing the value of the dict doesn't cause it to be updated. - power_params = self.power_parameters.copy() + power_params = pod_power_parameters.copy() del power_params["password"] self.power_parameters = power_params self.version = discovered_pod.version @@ -1674,7 +1749,7 @@ class Pod(BMC): @transactional def gather_clients_and_machines(pod): machine_details = [ - (machine.id, machine.power_parameters) + (machine.id, machine.get_power_parameters()) for machine in Machine.objects.filter( bmc__id=pod.id ).select_related("bmc") diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py index ba2f710..b6a0590 100644 --- a/src/maasserver/models/node.py +++ b/src/maasserver/models/node.py @@ -182,7 +182,10 @@ from metadataserver.user_data import generate_user_data_for_status from provisioningserver.drivers.osystem import OperatingSystemRegistry from provisioningserver.drivers.pod import Capabilities from provisioningserver.drivers.power.ipmi import IPMI_BOOT_TYPE -from provisioningserver.drivers.power.registry import PowerDriverRegistry +from provisioningserver.drivers.power.registry import ( + PowerDriverRegistry, + sanitise_power_parameters, +) from provisioningserver.events import EVENT_DETAILS, EVENT_TYPES from provisioningserver.logger import get_maas_logger, LegacyLogger from provisioningserver.refresh.node_info_scripts import ( @@ -248,7 +251,7 @@ def get_bios_boot_from_bmc(bmc): """ if bmc is None or bmc.power_type != "ipmi": return None - power_boot_type = bmc.power_parameters.get("power_boot_type") + power_boot_type = bmc.get_power_parameters().get("power_boot_type") if power_boot_type == IPMI_BOOT_TYPE.EFI: return "uefi" elif power_boot_type == IPMI_BOOT_TYPE.LEGACY: @@ -1386,7 +1389,7 @@ class Node(CleanSave, TimestampedModel): current BMC, so if the BMC is a chassis, the configuration will apply to all connected nodes. """ - from maasserver.models.bmc import BMC + from maasserver.models.bmc import BMC, create_bmc, get_or_create_bmc created_by_commissioning = from_commissioning old_bmc = self.bmc @@ -1395,7 +1398,7 @@ class Node(CleanSave, TimestampedModel): ) if power_type == self.power_type: if power_type == "manual": - self.bmc.power_parameters = bmc_params + self.bmc.set_power_parameters(bmc_params) self.bmc.save() else: existing_bmc = BMC.objects.filter( @@ -1408,10 +1411,10 @@ class Node(CleanSave, TimestampedModel): node.save() self.bmc = existing_bmc elif not existing_bmc: - self.bmc.power_parameters = bmc_params + self.bmc.set_power_parameters(bmc_params) self.bmc.save() elif chassis: - self.bmc, _ = BMC.objects.get_or_create( + self.bmc, _ = get_or_create_bmc( power_type=power_type, power_parameters=bmc_params, defaults={ @@ -1419,14 +1422,14 @@ class Node(CleanSave, TimestampedModel): }, ) else: - self.bmc = BMC.objects.create( + self.bmc = create_bmc( power_type=power_type, power_parameters=bmc_params, created_by_commissioning=created_by_commissioning, ) self.bmc.save() - self.instance_power_parameters = node_params or {} + self.set_instance_power_parameters(node_params or {}) # delete the old bmc if no node is connected to it if old_bmc and old_bmc != self.bmc and not old_bmc.node_set.exists(): @@ -1436,23 +1439,47 @@ class Node(CleanSave, TimestampedModel): def power_type(self): return "" if self.bmc is None else self.bmc.power_type - @property - def power_parameters(self): + def get_instance_power_parameters(self): + from maasserver.secrets import SecretManager + + power_parameters = self.instance_power_parameters or {} + return { + **power_parameters, + **SecretManager().get_composite_secret( + "power-parameters", obj=self.as_node(), default={} + ), + } + + def set_instance_power_parameters(self, power_parameters): + power_parameters, secrets = sanitise_power_parameters( + self.power_type, power_parameters + ) + from maasserver.secrets import SecretManager + + if secrets: + SecretManager().set_composite_secret( + "power-parameters", secrets, obj=self.as_node() + ) + self.instance_power_parameters = power_parameters + + def get_power_parameters(self): # Overlay instance power parameters over bmc power parameters. - instance_parameters = self.instance_power_parameters + instance_parameters = self.get_instance_power_parameters() if not instance_parameters: instance_parameters = {} bmc_parameters = {} - if self.bmc and self.bmc.power_parameters: - bmc_parameters = self.bmc.power_parameters + if self.bmc and ( + bmc_power_parameters := self.bmc.get_power_parameters() + ): + bmc_parameters = bmc_power_parameters return {**bmc_parameters, **instance_parameters} @property def instance_name(self): """Return the name of the VM instance for this machine, or None.""" - return self.instance_power_parameters.get( + return self.get_instance_power_parameters().get( "instance_name" - ) or self.instance_power_parameters.get( # for LXD + ) or self.get_instance_power_parameters().get( # for LXD "power_id" ) # for virsh @@ -2994,7 +3021,7 @@ class Node(CleanSave, TimestampedModel): d.addCallback( decompose_machine, pod.power_type, - self.power_parameters, + self.get_power_parameters(), pod_id=pod.id, name=pod.name, ) @@ -3098,7 +3125,7 @@ class Node(CleanSave, TimestampedModel): def get_effective_power_parameters(self): """Return effective power parameters, including any defaults.""" - power_params = self.power_parameters.copy() + power_params = self.get_power_parameters().copy() power_params.setdefault("system_id", self.system_id) # TODO: This default ought to be in the virsh template. diff --git a/src/maasserver/models/tests/test_bmc.py b/src/maasserver/models/tests/test_bmc.py index 183da8a..eb4b0b6 100644 --- a/src/maasserver/models/tests/test_bmc.py +++ b/src/maasserver/models/tests/test_bmc.py @@ -54,6 +54,7 @@ from maasserver.models.virtualmachine import ( ) from maasserver.models.vmcluster import VMCluster from maasserver.permissions import PodPermission +from maasserver.secrets import SecretManager from maasserver.testing.factory import factory from maasserver.testing.fixtures import RBACEnabled from maasserver.testing.matchers import MatchesSetwiseWithAll @@ -229,10 +230,12 @@ class TestBMC(MAASServerTestCase): sticky_ip = factory.make_StaticIPAddress( alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet ) - bmc.power_parameters = { - "power_address": "protocol://%s:8080/path/to/thing#tag" - % (factory.ip_to_url_format(sticky_ip.ip)) - } + bmc.set_power_parameters( + { + "power_address": "protocol://%s:8080/path/to/thing#tag" + % (factory.ip_to_url_format(sticky_ip.ip)) + } + ) bmc.save() self.assertEqual(sticky_ip.ip, bmc.ip_address.ip) self.assertEqual(subnet, bmc.ip_address.subnet) @@ -318,10 +321,12 @@ class TestBMC(MAASServerTestCase): new_ip_address.delete() self.assertNotEqual(new_ip, old_ip) - bmc.power_parameters = { - "power_address": "protocol://%s:8080/path/to/thing#tag" - % (factory.ip_to_url_format(new_ip)) - } + bmc.set_power_parameters( + { + "power_address": "protocol://%s:8080/path/to/thing#tag" + % (factory.ip_to_url_format(new_ip)) + } + ) bmc.save() # Check Machine has old IP address and kept same instance: machine_ip. @@ -351,10 +356,12 @@ class TestBMC(MAASServerTestCase): machine, bmc, machine_ip = self.make_machine_and_bmc_differing_ips() # Now change the BMC's address to match machine's. - bmc.power_parameters = { - "power_address": "protocol://%s:8080/path/to/thing#tag" - % (factory.ip_to_url_format(machine_ip.ip)) - } + bmc.set_power_parameters( + { + "power_address": "protocol://%s:8080/path/to/thing#tag" + % (factory.ip_to_url_format(machine_ip.ip)) + } + ) bmc.save() # Make sure BMC and Machine are using same StaticIPAddress instance. @@ -374,6 +381,20 @@ class TestBMC(MAASServerTestCase): bmc.delete() self.assertEqual(0, StaticIPAddress.objects.filter(id=ip.id).count()) + def test_delete_deletes_bmc_secrets(self): + bmc = factory.make_BMC() + secret_manager = SecretManager() + secret_manager.set_composite_secret( + "power-parameters", {"foo": "bar"}, obj=bmc + ) + + bmc.delete() + self.assertIsNone( + secret_manager.get_simple_secret( + "power-parameters", obj=bmc, default=None + ) + ) + def test_delete_bmc_spares_non_orphaned_ip_address(self): machine, bmc, machine_ip = self.make_machine_and_bmc_with_shared_ip() bmc.delete() @@ -616,6 +637,15 @@ class TestBMC(MAASServerTestCase): HasLength(len(non_routable_racks)), ) + def test_get_power_params(self): + bmc = factory.make_BMC() + secret_manager = SecretManager() + secret_manager.set_composite_secret( + "power-parameters", {"foo": "bar"}, obj=bmc + ) + + self.assertEqual({"foo": "bar"}, bmc.get_power_parameters()) + class TestPodManager(MAASServerTestCase): def enable_rbac(self): @@ -1023,7 +1053,7 @@ class TestPod(MAASServerTestCase, PodTestMixin): ) self.patch(pod, "sync_machines") pod.sync(discovered, factory.make_User()) - self.assertNotIn("password", pod.power_parameters) + self.assertNotIn("password", pod.get_power_parameters()) def test_sync_pod_creates_new_machines_connected_to_default_vlan(self): discovered = self.make_discovered_pod() @@ -1065,7 +1095,7 @@ class TestPod(MAASServerTestCase, PodTestMixin): created_machine.power_state, discovered_machine.power_state ) self.assertEqual( - created_machine.instance_power_parameters, + created_machine.get_instance_power_parameters(), discovered_machine.power_parameters, ) self.assertFalse(created_machine.dynamic) @@ -1243,7 +1273,7 @@ class TestPod(MAASServerTestCase, PodTestMixin): self.assertEqual( discovered_default.name, pod.default_storage_pool.name ) - self.assertEqual({}, pod.power_parameters) + self.assertEqual({}, pod.get_power_parameters()) def test_sync_pod_sets_default_numanode(self): discovered_bdev = self.make_discovered_block_device() @@ -1983,7 +2013,7 @@ class TestPod(MAASServerTestCase, PodTestMixin): created_machine.power_state, discovered_machine.power_state ) self.assertEqual( - created_machine.instance_power_parameters, + created_machine.get_instance_power_parameters(), discovered_machine.power_parameters, ) self.assertFalse(created_machine.dynamic) @@ -3059,19 +3089,21 @@ class TestPodDelete(MAASTransactionServerTestCase, PodTestMixin): bmc_module, "getClientFromIdentifiers" ).return_value = client yield pod.async_delete(decompose=True) + + power_parameters = yield deferToDatabase(pod.get_power_parameters) client.assert_has_calls( [ call( DecomposeMachine, type=pod.power_type, - context=pod.power_parameters, + context=power_parameters, pod_id=pod.id, name=pod.name, ), call( DecomposeMachine, type=pod.power_type, - context=pod.power_parameters, + context=power_parameters, pod_id=pod.id, name=pod.name, ), diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py index 88e5246..85ce521 100644 --- a/src/maasserver/models/tests/test_node.py +++ b/src/maasserver/models/tests/test_node.py @@ -1106,12 +1106,12 @@ class TestNode(MAASServerTestCase): def test_instance_name_virsh_vm(self): node = factory.make_Node() - node.instance_power_parameters = {"power_id": "vm"} + node.set_instance_power_parameters({"power_id": "vm"}) self.assertEqual(node.instance_name, "vm") def test_instance_name_lxd_vm(self): node = factory.make_Node() - node.instance_power_parameters = {"instance_name": "vm"} + node.set_instance_power_parameters({"instance_name": "vm"}) self.assertEqual(node.instance_name, "vm") def test_default_pool_for_machine(self): @@ -6140,11 +6140,11 @@ class TestNodePowerParameters(MAASServerTestCase): ) node.save() node = reload_object(node) - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) def test_power_parameters_default(self): node = factory.make_Node(power_type=None) - self.assertEqual({}, node.power_parameters) + self.assertEqual({}, node.get_power_parameters()) self.assertIsNone(node.bmc) def test_power_parameters_from_commissioning_not_new(self): @@ -6215,9 +6215,9 @@ class TestNodePowerParameters(MAASServerTestCase): node.set_power_config("hmc", parameters) node.save() node = reload_object(node) - self.assertEqual(parameters, node.power_parameters) - self.assertEqual(node_parameters, node.instance_power_parameters) - self.assertEqual(bmc_parameters, node.bmc.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) + self.assertEqual(node_parameters, node.get_instance_power_parameters()) + self.assertEqual(bmc_parameters, node.bmc.get_power_parameters()) self.assertEqual("hmc", node.bmc.power_type) self.assertEqual(node.power_type, node.bmc.power_type) self.assertEqual(ip_address, node.bmc.ip_address.ip) @@ -6235,7 +6235,7 @@ class TestNodePowerParameters(MAASServerTestCase): node.save() node = reload_object(node) self.assertEqual("manual", node.bmc.power_type) - self.assertEqual({}, node.bmc.power_parameters) + self.assertEqual({}, node.bmc.get_power_parameters()) self.assertTrue(BMC.objects.filter(power_type="manual")) def test_power_type_does_not_create_new_bmc_for_already_manual(self): @@ -6270,9 +6270,9 @@ class TestNodePowerParameters(MAASServerTestCase): node.set_power_config("virsh", parameters) node.save() node = reload_object(node) - self.assertEqual(parameters, node.power_parameters) - self.assertEqual(node_parameters, node.instance_power_parameters) - self.assertEqual(bmc_parameters, node.bmc.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) + self.assertEqual(node_parameters, node.get_instance_power_parameters()) + self.assertEqual(bmc_parameters, node.bmc.get_power_parameters()) self.assertEqual("10.0.2.1", node.bmc.ip_address.ip) def test_unknown_power_parameter_stored_on_node(self): @@ -6285,9 +6285,9 @@ class TestNodePowerParameters(MAASServerTestCase): node.set_power_config("hmc", parameters) node.save() node = reload_object(node) - self.assertEqual(parameters, node.power_parameters) - self.assertEqual(node_parameters, node.instance_power_parameters) - self.assertEqual(bmc_parameters, node.bmc.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) + self.assertEqual(node_parameters, node.get_instance_power_parameters()) + self.assertEqual(bmc_parameters, node.bmc.get_power_parameters()) def test_none_chassis_bmc_doesnt_consolidate(self): for _ in range(3): @@ -6308,9 +6308,11 @@ class TestNodePowerParameters(MAASServerTestCase): node.set_power_config("apc", parameters) node.save() node = reload_object(node) - self.assertEqual(parameters, node.power_parameters) - self.assertEqual(node_parameters, node.instance_power_parameters) - self.assertEqual(bmc_parameters, node.bmc.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) + self.assertEqual( + node_parameters, node.get_instance_power_parameters() + ) + self.assertEqual(bmc_parameters, node.bmc.get_power_parameters()) self.assertEqual("apc", node.bmc.power_type) nodes.append(node) @@ -6361,7 +6363,7 @@ class TestNodePowerParameters(MAASServerTestCase): # Set new BMC's values back to match original BMC, and make # sure the BMC count decreases as they consolidate. - parameters = nodes[0].power_parameters + parameters = nodes[0].get_power_parameters() parameters["power_id"] = factory.make_string() nodes[1].set_power_config(nodes[0].power_type, parameters) nodes[1].save() @@ -6377,7 +6379,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = dict(power_address=ip_address) node.set_power_config("hmc", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertEqual(ip_address, node.bmc.ip_address.ip) def test_power_parameters_unexpected_values_tolerated(self): @@ -6385,7 +6387,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = {factory.make_string(): factory.make_string()} node.set_power_config("virsh", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertIsNone(node.bmc.ip_address) def test_power_parameters_blank_ip_address_tolerated(self): @@ -6393,7 +6395,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = dict(power_address="") node.set_power_config("hmc", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertIsNone(node.bmc.ip_address) def test_power_parameters_ip_address_reset(self): @@ -6402,7 +6404,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = dict(power_address=ip_address) node.set_power_config("hmc", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertEqual(ip_address, node.bmc.ip_address.ip) # StaticIPAddress can be changed after being set. @@ -6410,7 +6412,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = dict(power_address=ip_address) node.set_power_config("hmc", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertEqual(ip_address, node.bmc.ip_address.ip) # StaticIPAddress can be made None after being set. @@ -6418,7 +6420,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = dict(power_address="") node.set_power_config("hmc", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertIsNone(node.bmc.ip_address) # StaticIPAddress can be changed after being made None. @@ -6426,7 +6428,7 @@ class TestNodePowerParameters(MAASServerTestCase): parameters = dict(power_address=ip_address) node.set_power_config("hmc", parameters) node.save() - self.assertEqual(parameters, node.power_parameters) + self.assertEqual(parameters, node.get_power_parameters()) self.assertEqual(ip_address, node.bmc.ip_address.ip) def test_orphaned_bmcs_are_removed(self): @@ -6650,9 +6652,9 @@ class TestDecomposeMachineTransactional( client.return_value = defer.succeed({"hints": hints}) machine = factory.make_Node(**kwargs) machine.bmc = pod - machine.instance_power_parameters = { - "power_id": factory.make_name("power_id") - } + machine.set_instance_power_parameters( + {"power_id": factory.make_name("power_id")} + ) return pod, machine, hints, client def test_delete_deletes_virtual_machine(self): @@ -6675,7 +6677,7 @@ class TestDecomposeMachineTransactional( MockCalledOnceWith( DecomposeMachine, type=pod.power_type, - context=machine.power_parameters, + context=machine.get_power_parameters(), pod_id=pod.id, name=pod.name, ), @@ -6724,7 +6726,7 @@ class TestDecomposeMachineTransactional( MockCalledOnceWith( DecomposeMachine, type=pod.power_type, - context=machine.power_parameters, + context=machine.get_power_parameters(), pod_id=pod.id, name=pod.name, ), diff --git a/src/maasserver/rpc/tests/test_nodes.py b/src/maasserver/rpc/tests/test_nodes.py index f39ce04..dee97b7 100644 --- a/src/maasserver/rpc/tests/test_nodes.py +++ b/src/maasserver/rpc/tests/test_nodes.py @@ -78,7 +78,7 @@ class TestCreateNode(MAASTransactionServerTestCase): self.assertEqual( (architecture, "manual", {}), - (node.architecture, node.power_type, node.power_parameters), + (node.architecture, node.power_type, node.get_power_parameters()), ) # Node will not have an auto-generated name because migrations are @@ -109,7 +109,7 @@ class TestCreateNode(MAASTransactionServerTestCase): ( node.architecture, node.power_type, - node.power_parameters, + node.get_power_parameters(), node.hostname, ), ) @@ -162,7 +162,7 @@ class TestCreateNode(MAASTransactionServerTestCase): ( node.architecture, node.power_type, - node.power_parameters, + node.get_power_parameters(), node.domain.id, node.hostname, ), @@ -241,7 +241,7 @@ class TestCreateNode(MAASTransactionServerTestCase): # Reload the object from the DB so that we're sure its power # parameters are being persisted. node = reload_object(node) - self.assertEqual(power_parameters, node.power_parameters) + self.assertEqual(power_parameters, node.get_power_parameters()) def test_forces_generic_subarchitecture_if_missing(self): self.prepare_rack_rpc() diff --git a/src/maasserver/secrets.py b/src/maasserver/secrets.py index 0607581..c404358 100644 --- a/src/maasserver/secrets.py +++ b/src/maasserver/secrets.py @@ -3,7 +3,7 @@ from typing import Any, Literal, NamedTuple, Optional from django.db.models import Model from hvac.exceptions import InvalidPath -from maasserver.models import Node, RootKey, Secret, VaultSecret +from maasserver.models import BMC, Node, RootKey, Secret, VaultSecret from maasserver.vault import get_region_vault_client_if_enabled, VaultClient SIMPLE_SECRET_KEY = "secret" @@ -15,7 +15,7 @@ class UnknownSecret(Exception): def __init__(self, name: str, obj: Optional[Model] = None): self.name = name self.obj = obj - message = f"Unkownn secret '{name}'" + message = f"Unknown secret '{name}'" if obj is not None: message += f" for object {type(obj)}" super().__init__(message) @@ -43,8 +43,9 @@ class ModelSecret(NamedTuple): MODEL_SECRETS = { secret.model: secret for secret in ( - ModelSecret(Node, "node", ["deploy-metadata"]), + ModelSecret(Node, "node", ["deploy-metadata", "power-parameters"]), ModelSecret(RootKey, "rootkey", ["material"]), + ModelSecret(BMC, "bmc", ["power-parameters"]), ) } diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py index 6b5cbc4..65414ca 100644 --- a/src/maasserver/testing/factory.py +++ b/src/maasserver/testing/factory.py @@ -532,7 +532,7 @@ class Factory(maastesting.factory.Factory): ip_address = existing_static_ips[0] bmc_ip_address = self.pick_ip_in_Subnet(ip_address.subnet) power_params = { - **node.power_parameters, + **node.get_power_parameters(), "power_address": "qemu+ssh://user@%s/system" % (factory.ip_to_url_format(bmc_ip_address)), "power_id": factory.make_name("power_id"), diff --git a/src/maasserver/triggers/tests/test_websocket_listener.py b/src/maasserver/triggers/tests/test_websocket_listener.py index 0fe50c8..1506d30 100644 --- a/src/maasserver/triggers/tests/test_websocket_listener.py +++ b/src/maasserver/triggers/tests/test_websocket_listener.py @@ -4399,7 +4399,7 @@ class TestBMCListener( def update_node(system_id): node = Node.objects.get(system_id=system_id) - node.bmc.power_parameters = {"power_address": "5.6.7.8"} + node.bmc.set_power_parameters({"power_address": "5.6.7.8"}) node.bmc.save() yield listener.startService() diff --git a/src/maasserver/vmhost.py b/src/maasserver/vmhost.py index 9ee1f73..05af032 100644 --- a/src/maasserver/vmhost.py +++ b/src/maasserver/vmhost.py @@ -53,7 +53,7 @@ def request_commissioning_results(pod): pod.name, pod.power_type, node.system_id, - pod.power_parameters, + pod.get_power_parameters(), token.consumer.key, token.key, token.secret, diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py index 6510006..87b575e 100644 --- a/src/maasserver/websockets/handlers/controller.py +++ b/src/maasserver/websockets/handlers/controller.py @@ -197,8 +197,8 @@ class ControllerHandler(NodeHandler): data["vlan_ids"] = vlan_ids # include certificate info if present - certificate = obj.power_parameters.get("certificate") - key = obj.power_parameters.get("key") + certificate = obj.get_power_parameters().get("certificate") + key = obj.get_power_parameters().get("key") if certificate and key: cert = Certificate.from_pem(certificate, key) data["certificate"] = dehydrate_certificate(cert) diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py index 058c433..dd4e85c 100644 --- a/src/maasserver/websockets/handlers/machine.py +++ b/src/maasserver/websockets/handlers/machine.py @@ -435,8 +435,8 @@ class MachineHandler(NodeHandler): data["devices"] = sorted(devices, key=itemgetter("fqdn")) # include certificate info if present - certificate = obj.power_parameters.get("certificate") - key = obj.power_parameters.get("key") + certificate = obj.get_power_parameters().get("certificate") + key = obj.get_power_parameters().get("key") if certificate and key: cert = Certificate.from_pem(certificate, key) data["certificate"] = dehydrate_certificate(cert) diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py index 73a3862..218a113 100644 --- a/src/maasserver/websockets/handlers/node.py +++ b/src/maasserver/websockets/handlers/node.py @@ -448,7 +448,7 @@ class NodeHandler(TimestampedModelHandler): data["power_type"] = obj.power_type data["power_parameters"] = self.dehydrate_power_parameters( - obj.power_parameters + obj.get_power_parameters() ) data["power_bmc_node_count"] = ( obj.bmc.node_set.count() if (obj.bmc is not None) else 0 diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py index b7832ee..683cac2 100644 --- a/src/maasserver/websockets/handlers/tests/test_controller.py +++ b/src/maasserver/websockets/handlers/tests/test_controller.py @@ -169,7 +169,7 @@ class TestControllerHandler(MAASServerTestCase): # and slowing down the client waiting for the response. self.assertEqual( queries, - 31, + 33, "Number of queries has changed; make sure this is expected.", ) diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py index bf3a453..e8045c9 100644 --- a/src/maasserver/websockets/handlers/tests/test_machine.py +++ b/src/maasserver/websockets/handlers/tests/test_machine.py @@ -342,7 +342,7 @@ class TestMachineHandler(MAASServerTestCase): "owner": handler.dehydrate_owner(node.owner), "parent": node.parent, "power_parameters": handler.dehydrate_power_parameters( - node.power_parameters + node.get_power_parameters() ), "power_bmc_node_count": node.bmc.node_set.count() if (node.bmc is not None) @@ -895,7 +895,7 @@ class TestMachineHandler(MAASServerTestCase): queries, _ = count_queries(handler.get, {"system_id": node.system_id}) self.assertEqual( queries, - 54, + 60, "Number of queries has changed; make sure this is expected.", ) diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py index 4669ee4..0387813 100644 --- a/src/metadataserver/api.py +++ b/src/metadataserver/api.py @@ -358,10 +358,10 @@ def store_node_power_parameters(node, request): except ValueError: raise MAASAPIBadRequest("Failed to parse JSON power_parameters") else: - power_parameters = node.power_parameters + power_parameters = node.get_power_parameters() if power_type == "redfish": power_parameters = { - **node.instance_power_parameters, + **node.get_instance_power_parameters(), **power_parameters, } node.set_power_config( diff --git a/src/metadataserver/tests/test_api.py b/src/metadataserver/tests/test_api.py index d6c388b..4a1fd29 100644 --- a/src/metadataserver/tests/test_api.py +++ b/src/metadataserver/tests/test_api.py @@ -3102,7 +3102,7 @@ class TestCommissioningAPI(MAASServerTestCase): ) node = reload_object(node) self.assertEqual("mscm", node.power_type) - self.assertNotEqual(params, node.power_parameters) + self.assertNotEqual(params, node.get_power_parameters()) def test_signal_refuses_bad_power_type(self): node = factory.make_Node( @@ -3134,7 +3134,7 @@ class TestCommissioningAPI(MAASServerTestCase): ) node = reload_object(node) self.assertEqual("ipmi", node.power_type) - self.assertEqual(params, node.power_parameters) + self.assertEqual(params, node.get_power_parameters()) def test_signal_power_type_lower_case_works(self): node = factory.make_Node( @@ -3153,7 +3153,7 @@ class TestCommissioningAPI(MAASServerTestCase): http.client.OK, response.status_code, response.content ) node = reload_object(node) - self.assertEqual(params, node.power_parameters) + self.assertEqual(params, node.get_power_parameters()) def test_signal_invalid_power_parameters(self): node = factory.make_Node( @@ -4103,7 +4103,7 @@ class TestStoreNodeParameters(APITestCase.ForUser): self.node.set_power_config("", {}) store_node_power_parameters(self.node, self.request) self.assertEqual("", self.node.power_type) - self.assertEqual({}, self.node.power_parameters) + self.assertEqual({}, self.node.get_power_parameters()) self.save.assert_has_calls([]) def test_power_type_redfish(self): @@ -4116,8 +4116,8 @@ class TestStoreNodeParameters(APITestCase.ForUser): store_node_power_parameters(self.node, self.request) self.assertEqual("redfish", self.node.power_type) self.assertEqual( - {**power_parameters, **self.node.instance_power_parameters}, - self.node.power_parameters, + {**power_parameters, **self.node.get_instance_power_parameters()}, + self.node.get_power_parameters(), ) self.save.assert_has_calls([]) @@ -4127,7 +4127,8 @@ class TestStoreNodeParameters(APITestCase.ForUser): store_node_power_parameters(self.node, self.request) self.assertEqual("redfish", self.node.power_type) self.assertEqual( - self.node.instance_power_parameters, self.node.power_parameters + self.node.get_instance_power_parameters(), + self.node.get_power_parameters(), ) self.save.assert_has_calls([]) @@ -4140,9 +4141,10 @@ class TestStoreNodeParameters(APITestCase.ForUser): } store_node_power_parameters(self.node, self.request) self.assertEqual("redfish", self.node.power_type) - self.assertEqual(self.node.power_parameters, power_parameters) + self.assertEqual(self.node.get_power_parameters(), power_parameters) self.assertEqual( - self.node.instance_power_parameters, self.node.power_parameters + self.node.get_instance_power_parameters(), + self.node.get_power_parameters(), ) self.save.assert_called_once_with() @@ -4154,7 +4156,7 @@ class TestStoreNodeParameters(APITestCase.ForUser): self.request.POST = {"power_type": "virsh"} store_node_power_parameters(self.node, self.request) self.assertEqual(self.node.power_type, "virsh") - self.assertEqual({"some": "param"}, self.node.power_parameters) + self.assertEqual({"some": "param"}, self.node.get_power_parameters()) self.save.assert_called_once_with() def test_power_type_set_with_parameters(self): @@ -4168,7 +4170,7 @@ class TestStoreNodeParameters(APITestCase.ForUser): } store_node_power_parameters(self.node, self.request) self.assertEqual(power_type, self.node.power_type) - self.assertEqual(power_parameters, self.node.power_parameters) + self.assertEqual(power_parameters, self.node.get_power_parameters()) self.assertTrue(self.node.bmc.created_by_commissioning) self.save.assert_called_once_with() diff --git a/src/provisioningserver/drivers/__init__.py b/src/provisioningserver/drivers/__init__.py index da0c9b2..e4b2f66 100644 --- a/src/provisioningserver/drivers/__init__.py +++ b/src/provisioningserver/drivers/__init__.py @@ -119,6 +119,7 @@ def make_setting_field( default=None, required=False, scope=SETTING_SCOPE.BMC, + secret=False, ): """Helper function for building a JSON setting parameters field. @@ -140,6 +141,9 @@ def make_setting_field( :param scope: 'bmc' or 'node' - Whether value is bmc or node specific. Defaults to 'bmc'. :type scope: string + :param secret: True or False - Whether value should be stored securely. + Defaults to False + :type secret: boolean """ if field_type not in ( "string", @@ -164,6 +168,7 @@ def make_setting_field( "choices": choices, "default": default, "scope": scope, + "secret": secret, } diff --git a/src/provisioningserver/drivers/pod/lxd.py b/src/provisioningserver/drivers/pod/lxd.py index ed46294..0181eaf 100644 --- a/src/provisioningserver/drivers/pod/lxd.py +++ b/src/provisioningserver/drivers/pod/lxd.py @@ -208,6 +208,7 @@ class LXDPodDriver(PodDriver): "LXD password (optional)", required=False, field_type="password", + secret=True, ), make_setting_field( "certificate", @@ -219,6 +220,7 @@ class LXDPodDriver(PodDriver): "LXD private key (optional)", required=False, field_type="password", + secret=True, ), ] ip_extractor = make_ip_extractor( diff --git a/src/provisioningserver/drivers/pod/virsh.py b/src/provisioningserver/drivers/pod/virsh.py index ffbbaf7..c490cbc 100644 --- a/src/provisioningserver/drivers/pod/virsh.py +++ b/src/provisioningserver/drivers/pod/virsh.py @@ -1285,6 +1285,7 @@ class VirshPodDriver(PodDriver): "Password (optional)", required=False, field_type="password", + secret=True, ), make_setting_field( "power_id", "Virsh VM ID", scope=SETTING_SCOPE.NODE, required=True diff --git a/src/provisioningserver/drivers/power/amt.py b/src/provisioningserver/drivers/power/amt.py index 300e160..cddebdd 100644 --- a/src/provisioningserver/drivers/power/amt.py +++ b/src/provisioningserver/drivers/power/amt.py @@ -51,7 +51,7 @@ class AMTPowerDriver(PowerDriver): description = "Intel AMT" settings = [ make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), make_setting_field("power_address", "Power address", required=True), ] diff --git a/src/provisioningserver/drivers/power/dli.py b/src/provisioningserver/drivers/power/dli.py index d5bd480..05deec7 100644 --- a/src/provisioningserver/drivers/power/dli.py +++ b/src/provisioningserver/drivers/power/dli.py @@ -38,7 +38,7 @@ class DLIPowerDriver(PowerDriver): make_setting_field("power_address", "Power address", required=True), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), ] ip_extractor = make_ip_extractor("power_address") diff --git a/src/provisioningserver/drivers/power/hmc.py b/src/provisioningserver/drivers/power/hmc.py index 4c773ef..f397f19 100644 --- a/src/provisioningserver/drivers/power/hmc.py +++ b/src/provisioningserver/drivers/power/hmc.py @@ -41,7 +41,7 @@ class HMCPowerDriver(PowerDriver): make_setting_field("power_address", "IP for HMC", required=True), make_setting_field("power_user", "HMC username"), make_setting_field( - "power_pass", "HMC password", field_type="password" + "power_pass", "HMC password", field_type="password", secret=True ), make_setting_field( "server_name", diff --git a/src/provisioningserver/drivers/power/hmcz.py b/src/provisioningserver/drivers/power/hmcz.py index 2645a53..f5072eb 100644 --- a/src/provisioningserver/drivers/power/hmcz.py +++ b/src/provisioningserver/drivers/power/hmcz.py @@ -48,7 +48,11 @@ class HMCZPowerDriver(PowerDriver): make_setting_field("power_address", "HMC Address", required=True), make_setting_field("power_user", "HMC username", required=True), make_setting_field( - "power_pass", "HMC password", field_type="password", required=True + "power_pass", + "HMC password", + field_type="password", + required=True, + secret=True, ), make_setting_field( "power_partition_name", diff --git a/src/provisioningserver/drivers/power/ipmi.py b/src/provisioningserver/drivers/power/ipmi.py index 70201e8..09f3295 100644 --- a/src/provisioningserver/drivers/power/ipmi.py +++ b/src/provisioningserver/drivers/power/ipmi.py @@ -276,9 +276,11 @@ class IPMIPowerDriver(PowerDriver): make_setting_field("power_address", "IP address", required=True), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True + ), + make_setting_field( + "k_g", "K_g BMC key", field_type="password", secret=True ), - make_setting_field("k_g", "K_g BMC key", field_type="password"), make_setting_field( "cipher_suite_id", "Cipher Suite ID", diff --git a/src/provisioningserver/drivers/power/moonshot.py b/src/provisioningserver/drivers/power/moonshot.py index c5f21c5..44e45e3 100644 --- a/src/provisioningserver/drivers/power/moonshot.py +++ b/src/provisioningserver/drivers/power/moonshot.py @@ -31,7 +31,7 @@ class MoonshotIPMIPowerDriver(PowerDriver): make_setting_field("power_address", "Power address", required=True), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), make_setting_field( "power_hwaddress", diff --git a/src/provisioningserver/drivers/power/mscm.py b/src/provisioningserver/drivers/power/mscm.py index 17862c8..1c93681 100644 --- a/src/provisioningserver/drivers/power/mscm.py +++ b/src/provisioningserver/drivers/power/mscm.py @@ -63,7 +63,10 @@ class MSCMPowerDriver(PowerDriver): ), make_setting_field("power_user", "MSCM CLI API user"), make_setting_field( - "power_pass", "MSCM CLI API password", field_type="password" + "power_pass", + "MSCM CLI API password", + field_type="password", + secret=True, ), make_setting_field( "node_id", diff --git a/src/provisioningserver/drivers/power/msftocs.py b/src/provisioningserver/drivers/power/msftocs.py index 2f8c8fe..8313f45 100644 --- a/src/provisioningserver/drivers/power/msftocs.py +++ b/src/provisioningserver/drivers/power/msftocs.py @@ -43,7 +43,7 @@ class MicrosoftOCSPowerDriver(PowerDriver): make_setting_field("power_port", "Power port"), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), make_setting_field( "blade_id", diff --git a/src/provisioningserver/drivers/power/nova.py b/src/provisioningserver/drivers/power/nova.py index 0eb26b4..fc9d7b1 100644 --- a/src/provisioningserver/drivers/power/nova.py +++ b/src/provisioningserver/drivers/power/nova.py @@ -48,7 +48,11 @@ class NovaPowerDriver(PowerDriver): make_setting_field("os_tenantname", "Tenant name", required=True), make_setting_field("os_username", "Username", required=True), make_setting_field( - "os_password", "Password", field_type="password", required=True + "os_password", + "Password", + field_type="password", + required=True, + secret=True, ), make_setting_field("os_authurl", "Auth URL", required=True), ] diff --git a/src/provisioningserver/drivers/power/openbmc.py b/src/provisioningserver/drivers/power/openbmc.py index b855399..2ffe406 100644 --- a/src/provisioningserver/drivers/power/openbmc.py +++ b/src/provisioningserver/drivers/power/openbmc.py @@ -51,6 +51,7 @@ class OpenBMCPowerDriver(PowerDriver): "OpenBMC password", field_type="password", required=True, + secret=True, ), ] ip_extractor = make_ip_extractor("power_address") diff --git a/src/provisioningserver/drivers/power/proxmox.py b/src/provisioningserver/drivers/power/proxmox.py index 9c427d4..a7b5635 100644 --- a/src/provisioningserver/drivers/power/proxmox.py +++ b/src/provisioningserver/drivers/power/proxmox.py @@ -47,12 +47,14 @@ class ProxmoxPowerDriver(WebhookPowerDriver): "Proxmox password, required if a token name and secret aren't " "given", field_type="password", + secret=True, ), make_setting_field("power_token_name", "Proxmox API token name"), make_setting_field( "power_token_secret", "Proxmox API token secret", field_type="password", + secret=True, ), make_setting_field( "power_vm_name", "Node ID", scope=SETTING_SCOPE.NODE, required=True diff --git a/src/provisioningserver/drivers/power/recs.py b/src/provisioningserver/drivers/power/recs.py index 8264471..ffddcbc 100644 --- a/src/provisioningserver/drivers/power/recs.py +++ b/src/provisioningserver/drivers/power/recs.py @@ -215,7 +215,7 @@ class RECSPowerDriver(PowerDriver): make_setting_field("power_port", "Power port"), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), ] ip_extractor = make_ip_extractor("power_address") diff --git a/src/provisioningserver/drivers/power/redfish.py b/src/provisioningserver/drivers/power/redfish.py index 19d9ecd..f554a22 100644 --- a/src/provisioningserver/drivers/power/redfish.py +++ b/src/provisioningserver/drivers/power/redfish.py @@ -166,6 +166,7 @@ class RedfishPowerDriver(RedfishPowerDriverBase): "Redfish password", field_type="password", required=True, + secret=True, ), make_setting_field("node_id", "Node ID", scope=SETTING_SCOPE.NODE), ] diff --git a/src/provisioningserver/drivers/power/registry.py b/src/provisioningserver/drivers/power/registry.py index 2cedfaf..4ffdae3 100644 --- a/src/provisioningserver/drivers/power/registry.py +++ b/src/provisioningserver/drivers/power/registry.py @@ -82,3 +82,37 @@ for driver in power_drivers: # Pod drivers are also power drivers. for driver_name, driver in PodDriverRegistry: PowerDriverRegistry.register_item(driver_name, driver) + + +def sanitise_power_parameters(power_type, power_parameters): + """Performs extraction of sensitive parameters and returns them separately. + Extraction relies on a `secret` flag of the power parameters property. + + :param power_type: BMC power driver type + :param power_parameters: BMC power parameters + """ + power_driver = PowerDriverRegistry.get_item(power_type) + + if not power_driver: + return power_parameters, {} + + parameters = power_parameters.copy() + secrets = {} + + for power_parameter in power_parameters.keys(): + setting = power_driver.get_setting(power_parameter) + if not setting: + continue + if not setting.get("secret"): + continue + + field_name = setting.get("name") + field_value = power_parameters.get(field_name) + + secrets = { + **secrets, + **{field_name: field_value}, + } + parameters.pop(field_name, None) + + return parameters, secrets diff --git a/src/provisioningserver/drivers/power/seamicro.py b/src/provisioningserver/drivers/power/seamicro.py index 83ec5c6..aa44929 100644 --- a/src/provisioningserver/drivers/power/seamicro.py +++ b/src/provisioningserver/drivers/power/seamicro.py @@ -49,7 +49,7 @@ class SeaMicroPowerDriver(PowerDriver): make_setting_field("power_address", "Power address", required=True), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), make_setting_field( "power_control", diff --git a/src/provisioningserver/drivers/power/ucsm.py b/src/provisioningserver/drivers/power/ucsm.py index 948ec3c..43c4b5c 100644 --- a/src/provisioningserver/drivers/power/ucsm.py +++ b/src/provisioningserver/drivers/power/ucsm.py @@ -39,7 +39,7 @@ class UCSMPowerDriver(PowerDriver): make_setting_field("power_address", "URL for XML API", required=True), make_setting_field("power_user", "API user"), make_setting_field( - "power_pass", "API password", field_type="password" + "power_pass", "API password", field_type="password", secret=True ), ] ip_extractor = make_ip_extractor( diff --git a/src/provisioningserver/drivers/power/vmware.py b/src/provisioningserver/drivers/power/vmware.py index 63799e7..21929a0 100644 --- a/src/provisioningserver/drivers/power/vmware.py +++ b/src/provisioningserver/drivers/power/vmware.py @@ -60,6 +60,7 @@ class VMwarePowerDriver(PowerDriver): "VMware password", field_type="password", required=True, + secret=True, ), make_setting_field( "power_port", "VMware API port (optional)", required=False diff --git a/src/provisioningserver/drivers/power/webhook.py b/src/provisioningserver/drivers/power/webhook.py index 11a92a9..ac61510 100644 --- a/src/provisioningserver/drivers/power/webhook.py +++ b/src/provisioningserver/drivers/power/webhook.py @@ -66,12 +66,13 @@ class WebhookPowerDriver(PowerDriver): ), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), make_setting_field( "power_token", "Power token, will be used in place of power_user and power_pass", field_type="password", + secret=True, ), make_setting_field( "power_verify_ssl", diff --git a/src/provisioningserver/drivers/power/wedge.py b/src/provisioningserver/drivers/power/wedge.py index f214032..9f723fb 100644 --- a/src/provisioningserver/drivers/power/wedge.py +++ b/src/provisioningserver/drivers/power/wedge.py @@ -33,7 +33,7 @@ class WedgePowerDriver(PowerDriver): make_setting_field("power_address", "IP address", required=True), make_setting_field("power_user", "Power user"), make_setting_field( - "power_pass", "Power password", field_type="password" + "power_pass", "Power password", field_type="password", secret=True ), ] ip_extractor = make_ip_extractor("power_address") diff --git a/src/provisioningserver/drivers/tests/test_base.py b/src/provisioningserver/drivers/tests/test_base.py index a7440e7..0b1a67b 100644 --- a/src/provisioningserver/drivers/tests/test_base.py +++ b/src/provisioningserver/drivers/tests/test_base.py @@ -388,6 +388,7 @@ class TestMakeSettingField(MAASTestCase): "default": default, "required": True, "scope": SETTING_SCOPE.NODE, + "secret": False, }, setting, )
-- Mailing list: https://launchpad.net/~sts-sponsors Post to : sts-sponsors@lists.launchpad.net Unsubscribe : https://launchpad.net/~sts-sponsors More help : https://help.launchpad.net/ListHelp