Skip to content

Commit f23766d

Browse files
weizhouapachemprokopchukDamans227
authored
core: introduce a set of type adapters (#13624)
* There is a set of TO classes with renamed fields, which makes impossible to correctly communicate between Management Servers and Agents * fix logger * Apply suggestion from Daman * Apply suggestion from Daman 2 * Fix nested TO renaming, log redaction and cleanup in compat TypeAdaptors AbstractTOAdaptor built its own private Gson to run the pre-rename serialization step through, which meant it never honoured the LoggingExclusionStrategy the enclosing Gson (GsonHelper's logging instance) was configured with, so fields marked @loglevel(Off) (e.g. VirtualMachineTO.vncPassword) leaked in plaintext when logged. It also had no adapters for the sibling compat TOs, so nested TOs (disks/nics inside a VirtualMachineTO, or a VirtualMachineTO inside a MigrateCommand) kept their new field names instead of being renamed for backward compatibility with older Agents. AbstractTOAdaptor no longer owns a Gson at all: it takes one via initGson(), mirroring the existing InterfaceTypeAdaptor pattern. GsonHelper.setDefaultGsonConfig now wires each compat adaptor's delegate Gson incrementally off the same builder, snapshotting it via builder.create() right before each adaptor registers itself, so every adaptor's delegate carries its sibling adaptors (for correct nested renaming) without ever routing back into itself and recursing forever. NetworkTO is now registered via registerTypeHierarchyAdapter since VirtualMachineTO.nics is declared as NicTO[] (a NetworkTO subclass) and was never matched by the previous exact-type registration. This also removes AbstractTOAdaptor's now-unused loggerBuilder/LOGGER and its duplicate copy of GsonHelper.setDefaultGsonConfig, and replaces a dead null check (getAsJsonObject() never returns null) with a real isJsonObject() check. Added RequestTest#testCompatFieldRenamingNestedTOs covering a StartCommand and a MigrateCommand with nested disks/nics, asserting old field names appear at every nesting level on the wire and that vncPassword never appears in the logging serialization. * fix logger * fix build error RequestTest * fix unit test failures RequestTest.java --------- Co-authored-by: mprokopchuk <mprokopchuk@gmail.com> Co-authored-by: Daman Arora <damans227@gmail.com>
1 parent ac8d69c commit f23766d

7 files changed

Lines changed: 380 additions & 1 deletion

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.transport.compat;
18+
19+
import com.cloud.utils.StringUtils;
20+
import com.cloud.utils.exception.CloudRuntimeException;
21+
import com.google.gson.Gson;
22+
import com.google.gson.JsonElement;
23+
import com.google.gson.JsonObject;
24+
import com.google.gson.JsonSerializationContext;
25+
import com.google.gson.JsonSerializer;
26+
27+
import java.lang.reflect.Type;
28+
import java.util.LinkedHashMap;
29+
import java.util.Map;
30+
31+
/**
32+
* JSON serializer adapter for transport classes (com.cloud.agent.api.to.*) that ensures backward compatibility
33+
* with older Agent versions due to rename of the fields
34+
* (see https://github.com/apache/cloudstack/pull/10514)
35+
*
36+
* This class does not build its own Gson instance: doing so would silently drop whichever exclusion
37+
* strategy (e.g. log redaction) and sibling compat adaptors (for nested TOs) the enclosing Gson was
38+
* configured with. Instead, whoever registers an instance of this class into a GsonBuilder is
39+
* responsible for also calling {@link #initGson(Gson)} with a Gson that (a) carries that same
40+
* exclusion strategy and (b) has adapters registered for any nested TO types that also need field
41+
* renaming, but not for this adaptor's own type (to avoid infinite recursion). See
42+
* {@link com.cloud.serializer.GsonHelper#setDefaultGsonConfig(com.google.gson.GsonBuilder)}.
43+
*/
44+
public class AbstractTOAdaptor<T> implements JsonSerializer<T> {
45+
private Gson gson;
46+
private Map<String, String> fieldMappings;
47+
48+
protected AbstractTOAdaptor(String... fields) {
49+
this.fieldMappings = new LinkedHashMap<>();
50+
for (int i = 0; i + 1 < fields.length; i += 2) {
51+
String sourceField = fields[i];
52+
String destinationField = fields[i + 1];
53+
// skip empty fields
54+
if (StringUtils.isBlank(sourceField) || StringUtils.isBlank(destinationField)) {
55+
continue;
56+
}
57+
this.fieldMappings.put(sourceField, destinationField);
58+
}
59+
if (this.fieldMappings.isEmpty()) {
60+
throw new CloudRuntimeException("Field mappings must not be empty");
61+
}
62+
}
63+
64+
public void initGson(Gson gson) {
65+
this.gson = gson;
66+
}
67+
68+
@Override
69+
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
70+
if (src == null) {
71+
return null;
72+
}
73+
JsonElement tree = gson.toJsonTree(src);
74+
if (tree.isJsonObject()) {
75+
JsonObject obj = tree.getAsJsonObject();
76+
for (Map.Entry<String, String> field : fieldMappings.entrySet()) {
77+
String sourceField = field.getKey();
78+
String destinationField = field.getValue();
79+
if (obj.has(sourceField)) {
80+
obj.add(destinationField, obj.get(sourceField));
81+
}
82+
}
83+
}
84+
return tree;
85+
}
86+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.transport.compat;
18+
19+
import com.cloud.agent.api.to.DiskTO;
20+
21+
/**
22+
* See {@link AbstractTOAdaptor}.
23+
*/
24+
public class DiskTOAdaptor extends AbstractTOAdaptor<DiskTO> {
25+
public DiskTOAdaptor() {
26+
super("details", "_details");
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.transport.compat;
18+
19+
import com.cloud.agent.api.MigrateCommand;
20+
21+
/**
22+
* See {@link AbstractTOAdaptor}.
23+
*/
24+
public class MigrateCommandAdaptor extends AbstractTOAdaptor<MigrateCommand> {
25+
public MigrateCommandAdaptor() {
26+
super("destinationIp", "destIp", "windows", "isWindows", "virtualMachine", "vmTO");
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.transport.compat;
18+
19+
import com.cloud.agent.api.to.NetworkTO;
20+
21+
/**
22+
* See {@link AbstractTOAdaptor}.
23+
*/
24+
public class NetworkTOAdaptor extends AbstractTOAdaptor<NetworkTO> {
25+
public NetworkTOAdaptor() {
26+
super("securityGroupEnabled", "isSecurityGroupEnabled");
27+
}
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain 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,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.agent.transport.compat;
18+
19+
import com.cloud.agent.api.to.VirtualMachineTO;
20+
21+
/**
22+
* See {@link AbstractTOAdaptor}.
23+
*/
24+
public class VirtualMachineTOAdaptor extends AbstractTOAdaptor<VirtualMachineTO> {
25+
26+
public VirtualMachineTOAdaptor() {
27+
super("details", "params");
28+
}
29+
}

‎core/src/main/java/com/cloud/serializer/GsonHelper.java‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121

2222
import java.util.List;
2323

24+
import com.cloud.agent.api.MigrateCommand;
25+
import com.cloud.agent.api.to.DiskTO;
26+
import com.cloud.agent.api.to.NetworkTO;
27+
import com.cloud.agent.api.to.VirtualMachineTO;
28+
import com.cloud.agent.transport.compat.DiskTOAdaptor;
29+
import com.cloud.agent.transport.compat.MigrateCommandAdaptor;
30+
import com.cloud.agent.transport.compat.NetworkTOAdaptor;
31+
import com.cloud.agent.transport.compat.VirtualMachineTOAdaptor;
2432
import com.cloud.hypervisor.Hypervisor;
2533
import org.apache.cloudstack.transport.HypervisorTypeAdaptor;
2634
import org.apache.logging.log4j.Logger;
@@ -78,6 +86,42 @@ public static Gson setDefaultGsonConfig(GsonBuilder builder) {
7886
}.getType(), new NwGroupsCommandTypeAdaptor());
7987
builder.registerTypeAdapter(Storage.StoragePoolType.class, new StoragePoolTypeAdaptor());
8088
builder.registerTypeAdapter(Hypervisor.HypervisorType.class, new HypervisorTypeAdaptor());
89+
90+
// added for compatibility purposes, remove after all Agents migrate to the new version
91+
//
92+
// Each compat adaptor below needs a "base" Gson to run its own reflective (pre-rename)
93+
// serialization through, so that nested TOs are renamed too and the exclusion strategy set
94+
// on `builder` (e.g. log redaction) is honoured consistently at every nesting level. That base
95+
// Gson is built incrementally off the same builder, snapshotted (via builder.create()) just
96+
// before each adaptor's own type is registered on it, so it carries every sibling adaptor it
97+
// can nest without ever routing back into itself and recursing forever.
98+
DiskTOAdaptor diskAdaptor = new DiskTOAdaptor();
99+
NetworkTOAdaptor netAdaptor = new NetworkTOAdaptor();
100+
VirtualMachineTOAdaptor vmAdaptor = new VirtualMachineTOAdaptor();
101+
MigrateCommandAdaptor migrateAdaptor = new MigrateCommandAdaptor();
102+
103+
// DiskTO and NetworkTO don't nest any other compat TO, so the plain config built so far is
104+
// already the correct base Gson for them.
105+
Gson leafDelegateGson = builder.create();
106+
diskAdaptor.initGson(leafDelegateGson);
107+
netAdaptor.initGson(leafDelegateGson);
108+
109+
// VirtualMachineTO nests DiskTO[] and NicTO[] (NicTO extends NetworkTO), so its base Gson needs
110+
// Disk/Network adapters too. registerTypeHierarchyAdapter is used for NetworkTO so that the
111+
// NicTO[]-declared "nics" field is matched via its supertype.
112+
builder.registerTypeAdapter(DiskTO.class, diskAdaptor);
113+
builder.registerTypeHierarchyAdapter(NetworkTO.class, netAdaptor);
114+
Gson vmDelegateGson = builder.create();
115+
vmAdaptor.initGson(vmDelegateGson);
116+
117+
// MigrateCommand nests a VirtualMachineTO, so its base Gson needs the VirtualMachineTO adapter
118+
// (which already renames the nested disks/nics above).
119+
builder.registerTypeAdapter(VirtualMachineTO.class, vmAdaptor);
120+
Gson migrateDelegateGson = builder.create();
121+
migrateAdaptor.initGson(migrateDelegateGson);
122+
123+
builder.registerTypeAdapter(MigrateCommand.class, migrateAdaptor);
124+
81125
Gson gson = builder.create();
82126
dsAdaptor.initGson(gson);
83127
dtAdaptor.initGson(gson);

0 commit comments

Comments
 (0)