New deployments do not take into account the new configurations (ephemeral_deployments, hw_sync etc..))

Bug #2033632 reported by Jacopo Rota
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
MAAS
Fix Committed
High
Alessandro Marcolini
3.3
Fix Committed
High
Christian Grabowski
3.4
Fix Released
High
Christian Grabowski
3.5
Fix Committed
High
Alessandro Marcolini

Bug Description

As per title, the steps to reproduce are:

1) deploy a machine with `ephemeral_deploy` enabled (or `hw_sync` and other properties) using the CLI or the HTTP calls
2) Release the machine
3) deploy again the machine, this time with `ephemeral_deploy` disabled

The machine will use the ephemeral_deployment (same for the other options) instead.

This is happening only in the HTTP handlers, not in the websocket handlers.

Related branches

Revision history for this message
Jacopo Rota (r00ta) wrote :

The root cause is at src/maasserver/api/machines.py.

In particular

```
        Form = get_machine_edit_form(request.user)
        form = Form(instance=machine, data={})
        if series is not None:
            form.set_distro_series(series=series)
        if license_key is not None:
            form.set_license_key(license_key=license_key)
        if hwe_kernel is not None:
            form.set_hwe_kernel(hwe_kernel=hwe_kernel)
        if options.install_rackd:
            form.set_install_rackd(install_rackd=options.install_rackd)
        if options.ephemeral_deploy:
            form.set_ephemeral_deploy(
                ephemeral_deploy=options.ephemeral_deploy
            )
        if options.enable_hw_sync:
            form.set_enable_hw_sync(enable_hw_sync=options.enable_hw_sync)
        if form.is_valid():
            form.save()
```
is wrong because in case the new deployment has these options set to False, they are not stored and the previous ones are used

Revision history for this message
Adam Collard (adam-collard) wrote :

That snippet doesn't seem inherently wrong?

`form = Form(instance=machine, data={})` won't that create a fresh clean form?

Revision history for this message
Jacopo Rota (r00ta) wrote (last edit ):

Just tried with this unit test and it fails
```

    def test_POST_multiple_deploy_override_options(self):
        self.patch(node_module.Node, "_start")
        self.patch(machines_module, "get_curtin_merged_config")
        osystem = Config.objects.get_config("default_osystem")
        distro_series = Config.objects.get_config("default_distro_series")
        make_usable_osystem(
            self, osystem_name=osystem, releases=[distro_series]
        )
        machine = factory.make_Node(
            owner=self.user,
            interface=True,
            status=NODE_STATUS.ALLOCATED,
            power_type="manual",
            distro_series=distro_series,
            osystem=osystem,
            architecture=make_usable_architecture(self),
        )

        response = self.client.post(
            self.get_machine_uri(machine), {"op": "deploy", "enable_hw_sync": True}
        )
        response_info = json_load_bytes(response.content)
        assert response_info["enable_hw_sync"] is True

        response = self.client.post(
            self.get_machine_uri(machine), {"op": "deploy"}
        )
        response_info = json_load_bytes(response.content)
        assert response_info["enable_hw_sync"] is False

```

Revision history for this message
Jacopo Rota (r00ta) wrote :

@adam we have some overrides: in particular `full_clean` is just setting the values specified in the Form and it will keep the existing at the old values.

```
class APIEditMixin:
    """A mixin that allows sane usage of Django's form machinery via the API.

    First it ensures that missing fields are not errors, then it removes
    None values from cleaned data. This means that missing fields result
    in no change instead of an error.

    :ivar submitted_data: The `data` as originally submitted.
    """

    def full_clean(self):
        """For missing fields, default to the model's existing value."""
        self.submitted_data = self.data
        self.data = get_overridden_query_dict(
            self.initial, self.data, self.fields
        )
        super().full_clean()
```

For example, patching it like

```
    def full_clean(self):
        """For missing fields, default to the model's existing value."""
        self.submitted_data = self.data
        print("DATA")
        print(self.data)
        print(self.initial)
        self.data = get_overridden_query_dict(
            self.initial, self.data, self.fields
        )
        print(self.data)
        super().full_clean()
```
and executing the test I posted above you will see

```
# FIRST DEPLOYMENT
self.data: {'enable_hw_sync': True}
self.initial: {'hostname': 'a5NPjpSsMxvbdTLVorz8', 'domain': 0, 'osystem': 'ubuntu', 'distro_series': 'ubuntu/focal', 'architecture': 'arch-xzgCJi/sub-sxIylM', 'min_hwe_kernel': None, 'hwe_kernel': None, 'swap_size': None, 'ephemeral_deploy': False, 'license_key': None, 'install_rackd': False, 'enable_hw_sync': False}
self.data: <QueryDict: {'hostname': ['a5NPjpSsMxvbdTLVorz8'], 'domain': [0], 'osystem': ['ubuntu'], 'distro_series': ['ubuntu/focal'], 'architecture': ['arch-xzgCJi/sub-sxIylM'], 'min_hwe_kernel': [None], 'hwe_kernel': [None], 'swap_size': [None], 'ephemeral_deploy': [False], 'license_key': [None], 'install_rackd': [False], 'enable_hw_sync': [True]}>

# SECOND DEPLOYMENT

self.data: {}
self.initial: {'hostname': 'a5NPjpSsMxvbdTLVorz8', 'domain': 0, 'osystem': 'ubuntu', 'distro_series': 'ubuntu/focal', 'architecture': 'arch-xzgCJi/sub-sxIylM', 'min_hwe_kernel': '', 'hwe_kernel': 'hwe-20.04', 'swap_size': None, 'ephemeral_deploy': False, 'license_key': '', 'install_rackd': False, 'enable_hw_sync': True}
self.data: <QueryDict: {'hostname': ['a5NPjpSsMxvbdTLVorz8'], 'domain': [0], 'osystem': ['ubuntu'], 'distro_series': ['ubuntu/focal'], 'architecture': ['arch-xzgCJi/sub-sxIylM'], 'min_hwe_kernel': [''], 'hwe_kernel': ['hwe-20.04'], 'swap_size': [None], 'ephemeral_deploy': [False], 'license_key': [''], 'install_rackd': [False], 'enable_hw_sync': [True]}>
```

no longer affects: maas/3.5
Changed in maas:
milestone: none → 3.5.0
importance: Medium → High
Changed in maas:
assignee: nobody → Björn Tillenius (bjornt)
Changed in maas:
milestone: 3.5.0 → 3.6.0
Revision history for this message
Björn Tillenius (bjornt) wrote :

I can't reproduce this

Changed in maas:
assignee: Björn Tillenius (bjornt) → nobody
status: Triaged → Incomplete
Revision history for this message
Jacopo Rota (r00ta) wrote :

`ephemeral_deployment` was fixed in the meantime. But `hw_sync` and others should be still affected

Changed in maas:
status: Incomplete → Triaged
assignee: nobody → Alessandro Marcolini (alemar99)
Changed in maas:
status: Triaged → Fix Committed
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.