Skip to content

Commit 99d9dfb

Browse files
fix(compute): reuse an existing node key when registering a Node
Node.insert() unconditionally created a NodeEncryptionKey for its uuid, so registering a compute Node for a machine that was already a local hypervisor's node (which provisions its own key the same way) crashed on the unique node uuid. Route through get_or_create like MachinePool already does.
1 parent 95cd90e commit 99d9dfb

3 files changed

Lines changed: 83 additions & 8 deletions

File tree

exordos_core/compute/dm/models.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import typing as tp
1919
import uuid as sys_uuid
2020

21-
from gcl_sdk.agents.universal.api import crypto as ua_crypto
2221
from gcl_sdk.agents.universal.dm import models as ua_models
2322
from gcl_sdk.infra.dm import models as infra_models
2423
import netaddr
@@ -427,13 +426,10 @@ def insert(self, session=None):
427426
volume = Volume.restore_from_simple_view(**view)
428427
volume.insert(session=session)
429428

430-
# Generate private key for the node
431-
_, key_base64 = ua_crypto.generate_key_base64()
432-
private_key = ua_models.NodeEncryptionKey(
433-
uuid=self.uuid,
434-
private_key=key_base64,
435-
)
436-
private_key.insert(session=session)
429+
# A key may already exist for this uuid (e.g. it's also
430+
# registered as a local hypervisor's node, which provisions its
431+
# own key the same way) - reuse it instead of conflicting.
432+
ua_models.NodeEncryptionKey.get_or_create(self.uuid, session=session)
437433

438434
def get_agent_private_key(self):
439435
enc_key = ua_models.NodeEncryptionKey.objects.get_one(

exordos_core/tests/functional/restapi/compute/test_hypervisor_api.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@
1515
# under the License.
1616

1717
import typing as tp
18+
import uuid as sys_uuid
1819

1920
from bazooka import exceptions as bazooka_exc
2021
from gcl_iam.tests.functional import clients as iam_clients
22+
from gcl_sdk.agents.universal.api import crypto as ua_crypto
23+
from gcl_sdk.agents.universal.dm import models as ua_models
2124
import pytest
25+
from restalchemy.dm import filters as dm_filters
2226

2327
from exordos_core.compute import constants as nc
2428

@@ -312,3 +316,34 @@ def test_hypervisors_add_same_connection_uri(
312316
client.delete(
313317
client.build_resource_uri(["compute", "hypervisors", hypervisor1["uuid"]])
314318
)
319+
320+
def test_node_reuses_an_existing_node_key(
321+
self,
322+
node_factory: tp.Callable,
323+
user_api_client: iam_clients.GenesisCoreTestRESTClient,
324+
auth_user_admin: iam_clients.GenesisCoreAuth,
325+
):
326+
# A key may already exist for a node's uuid from another source
327+
# (e.g. it's also registered as a local hypervisor's node) -
328+
# registering the Node must reuse it instead of conflicting on
329+
# insert.
330+
node_uuid = sys_uuid.uuid4()
331+
_, private_key = ua_crypto.generate_key_base64()
332+
existing_key = ua_models.NodeEncryptionKey(
333+
uuid=node_uuid, private_key=private_key
334+
)
335+
existing_key.insert()
336+
337+
client = user_api_client(auth_user_admin)
338+
node = node_factory(uuid=node_uuid)
339+
response = client.post(
340+
client.build_collection_uri(["compute", "nodes"]), json=node
341+
)
342+
assert response.status_code == 201
343+
344+
key = ua_models.NodeEncryptionKey.objects.get_one(
345+
filters={"uuid": dm_filters.EQ(node_uuid)}
346+
)
347+
assert key.private_key == private_key
348+
349+
client.delete(client.build_resource_uri(["compute", "nodes", str(node_uuid)]))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright 2026 Genesis Corporation.
2+
#
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
import uuid as sys_uuid
17+
from unittest.mock import patch
18+
19+
from gcl_sdk.agents.universal.dm import models as ua_models
20+
from gcl_sdk.infra.dm import models as infra_models
21+
22+
from exordos_core.compute.dm import models
23+
24+
25+
class TestNodeInsert:
26+
def test_reuses_an_existing_key(self):
27+
# A key may already exist for this uuid (e.g. it's also a local
28+
# hypervisor's node, which provisions its own key the same way) -
29+
# go through get_or_create instead of blindly inserting a fresh
30+
# one and conflicting on the unique node uuid.
31+
node = models.Node(
32+
cores=1,
33+
ram=1024,
34+
disk_spec=infra_models.RootDiskSpec(image="ubuntu_24.04"),
35+
project_id=sys_uuid.uuid4(),
36+
)
37+
38+
with (
39+
patch.object(models.orm.SQLStorableMixin, "insert"),
40+
patch.object(ua_models.NodeEncryptionKey, "get_or_create") as get_or_create,
41+
):
42+
node.insert()
43+
44+
get_or_create.assert_called_once_with(node.uuid, session=None)

0 commit comments

Comments
 (0)