This is an automated email from the ASF dual-hosted git repository.

weizhouapache pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new f23766d7c33 core: introduce a set of type adapters (#13624)
f23766d7c33 is described below

commit f23766d7c33b32a30caeb2d2b04039d0c6b24f13
Author: Wei Zhou <[email protected]>
AuthorDate: Thu Sep 24 12:48:18 2026 +0200

    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 <[email protected]>
    Co-authored-by: Daman Arora <[email protected]>
---
 .../agent/transport/compat/AbstractTOAdaptor.java  |  86 +++++++++++++
 .../agent/transport/compat/DiskTOAdaptor.java      |  28 +++++
 .../transport/compat/MigrateCommandAdaptor.java    |  28 +++++
 .../agent/transport/compat/NetworkTOAdaptor.java   |  28 +++++
 .../transport/compat/VirtualMachineTOAdaptor.java  |  29 +++++
 .../main/java/com/cloud/serializer/GsonHelper.java |  44 +++++++
 .../com/cloud/agent/transport/RequestTest.java     | 138 ++++++++++++++++++++-
 7 files changed, 380 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/com/cloud/agent/transport/compat/AbstractTOAdaptor.java 
b/core/src/main/java/com/cloud/agent/transport/compat/AbstractTOAdaptor.java
new file mode 100644
index 00000000000..ed5630183ea
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/transport/compat/AbstractTOAdaptor.java
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.transport.compat;
+
+import com.cloud.utils.StringUtils;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * JSON serializer adapter for transport classes (com.cloud.agent.api.to.*) 
that ensures backward compatibility
+ * with older Agent versions due to rename of the fields
+ * (see https://github.com/apache/cloudstack/pull/10514)
+ *
+ * This class does not build its own Gson instance: doing so would silently 
drop whichever exclusion
+ * strategy (e.g. log redaction) and sibling compat adaptors (for nested TOs) 
the enclosing Gson was
+ * configured with. Instead, whoever registers an instance of this class into 
a GsonBuilder is
+ * responsible for also calling {@link #initGson(Gson)} with a Gson that (a) 
carries that same
+ * exclusion strategy and (b) has adapters registered for any nested TO types 
that also need field
+ * renaming, but not for this adaptor's own type (to avoid infinite 
recursion). See
+ * {@link 
com.cloud.serializer.GsonHelper#setDefaultGsonConfig(com.google.gson.GsonBuilder)}.
+ */
+public class AbstractTOAdaptor<T> implements JsonSerializer<T> {
+    private Gson gson;
+    private Map<String, String> fieldMappings;
+
+    protected AbstractTOAdaptor(String... fields) {
+        this.fieldMappings = new LinkedHashMap<>();
+        for (int i = 0; i + 1 < fields.length; i += 2) {
+            String sourceField = fields[i];
+            String destinationField = fields[i + 1];
+            // skip empty fields
+            if (StringUtils.isBlank(sourceField) || 
StringUtils.isBlank(destinationField)) {
+                continue;
+            }
+            this.fieldMappings.put(sourceField, destinationField);
+        }
+        if (this.fieldMappings.isEmpty()) {
+            throw new CloudRuntimeException("Field mappings must not be 
empty");
+        }
+    }
+
+    public void initGson(Gson gson) {
+        this.gson = gson;
+    }
+
+    @Override
+    public JsonElement serialize(T src, Type typeOfSrc, 
JsonSerializationContext context) {
+        if (src == null) {
+            return null;
+        }
+        JsonElement tree = gson.toJsonTree(src);
+        if (tree.isJsonObject()) {
+            JsonObject obj = tree.getAsJsonObject();
+            for (Map.Entry<String, String> field : fieldMappings.entrySet()) {
+                String sourceField = field.getKey();
+                String destinationField = field.getValue();
+                if (obj.has(sourceField)) {
+                    obj.add(destinationField, obj.get(sourceField));
+                }
+            }
+        }
+        return tree;
+    }
+}
diff --git 
a/core/src/main/java/com/cloud/agent/transport/compat/DiskTOAdaptor.java 
b/core/src/main/java/com/cloud/agent/transport/compat/DiskTOAdaptor.java
new file mode 100644
index 00000000000..6b45529de05
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/transport/compat/DiskTOAdaptor.java
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.transport.compat;
+
+import com.cloud.agent.api.to.DiskTO;
+
+/**
+ * See {@link AbstractTOAdaptor}.
+ */
+public class DiskTOAdaptor extends AbstractTOAdaptor<DiskTO> {
+    public DiskTOAdaptor() {
+        super("details", "_details");
+    }
+}
diff --git 
a/core/src/main/java/com/cloud/agent/transport/compat/MigrateCommandAdaptor.java
 
b/core/src/main/java/com/cloud/agent/transport/compat/MigrateCommandAdaptor.java
new file mode 100644
index 00000000000..ddac8afe597
--- /dev/null
+++ 
b/core/src/main/java/com/cloud/agent/transport/compat/MigrateCommandAdaptor.java
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.transport.compat;
+
+import com.cloud.agent.api.MigrateCommand;
+
+/**
+ * See {@link AbstractTOAdaptor}.
+ */
+public class MigrateCommandAdaptor extends AbstractTOAdaptor<MigrateCommand> {
+    public MigrateCommandAdaptor() {
+        super("destinationIp", "destIp", "windows", "isWindows", 
"virtualMachine", "vmTO");
+    }
+}
diff --git 
a/core/src/main/java/com/cloud/agent/transport/compat/NetworkTOAdaptor.java 
b/core/src/main/java/com/cloud/agent/transport/compat/NetworkTOAdaptor.java
new file mode 100644
index 00000000000..01b0cdb4cdf
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/transport/compat/NetworkTOAdaptor.java
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.transport.compat;
+
+import com.cloud.agent.api.to.NetworkTO;
+
+/**
+ * See {@link AbstractTOAdaptor}.
+ */
+public class NetworkTOAdaptor extends AbstractTOAdaptor<NetworkTO> {
+    public NetworkTOAdaptor() {
+        super("securityGroupEnabled", "isSecurityGroupEnabled");
+    }
+}
diff --git 
a/core/src/main/java/com/cloud/agent/transport/compat/VirtualMachineTOAdaptor.java
 
b/core/src/main/java/com/cloud/agent/transport/compat/VirtualMachineTOAdaptor.java
new file mode 100644
index 00000000000..24a8c02a59c
--- /dev/null
+++ 
b/core/src/main/java/com/cloud/agent/transport/compat/VirtualMachineTOAdaptor.java
@@ -0,0 +1,29 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.agent.transport.compat;
+
+import com.cloud.agent.api.to.VirtualMachineTO;
+
+/**
+ * See {@link AbstractTOAdaptor}.
+ */
+public class VirtualMachineTOAdaptor extends 
AbstractTOAdaptor<VirtualMachineTO> {
+
+    public VirtualMachineTOAdaptor() {
+        super("details", "params");
+    }
+}
diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java 
b/core/src/main/java/com/cloud/serializer/GsonHelper.java
index 7de98c08b7e..7fcfb28f983 100644
--- a/core/src/main/java/com/cloud/serializer/GsonHelper.java
+++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java
@@ -21,6 +21,14 @@ package com.cloud.serializer;
 
 import java.util.List;
 
+import com.cloud.agent.api.MigrateCommand;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.agent.api.to.NetworkTO;
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.agent.transport.compat.DiskTOAdaptor;
+import com.cloud.agent.transport.compat.MigrateCommandAdaptor;
+import com.cloud.agent.transport.compat.NetworkTOAdaptor;
+import com.cloud.agent.transport.compat.VirtualMachineTOAdaptor;
 import com.cloud.hypervisor.Hypervisor;
 import org.apache.cloudstack.transport.HypervisorTypeAdaptor;
 import org.apache.logging.log4j.Logger;
@@ -78,6 +86,42 @@ public class GsonHelper {
         }.getType(), new NwGroupsCommandTypeAdaptor());
         builder.registerTypeAdapter(Storage.StoragePoolType.class, new 
StoragePoolTypeAdaptor());
         builder.registerTypeAdapter(Hypervisor.HypervisorType.class, new 
HypervisorTypeAdaptor());
+
+        // added for compatibility purposes, remove after all Agents migrate 
to the new version
+        //
+        // Each compat adaptor below needs a "base" Gson to run its own 
reflective (pre-rename)
+        // serialization through, so that nested TOs are renamed too and the 
exclusion strategy set
+        // on `builder` (e.g. log redaction) is honoured consistently at every 
nesting level. That base
+        // Gson is built incrementally off the same builder, snapshotted (via 
builder.create()) just
+        // before each adaptor's own type is registered on it, so it carries 
every sibling adaptor it
+        // can nest without ever routing back into itself and recursing 
forever.
+        DiskTOAdaptor diskAdaptor = new DiskTOAdaptor();
+        NetworkTOAdaptor netAdaptor = new NetworkTOAdaptor();
+        VirtualMachineTOAdaptor vmAdaptor = new VirtualMachineTOAdaptor();
+        MigrateCommandAdaptor migrateAdaptor = new MigrateCommandAdaptor();
+
+        // DiskTO and NetworkTO don't nest any other compat TO, so the plain 
config built so far is
+        // already the correct base Gson for them.
+        Gson leafDelegateGson = builder.create();
+        diskAdaptor.initGson(leafDelegateGson);
+        netAdaptor.initGson(leafDelegateGson);
+
+        // VirtualMachineTO nests DiskTO[] and NicTO[] (NicTO extends 
NetworkTO), so its base Gson needs
+        // Disk/Network adapters too. registerTypeHierarchyAdapter is used for 
NetworkTO so that the
+        // NicTO[]-declared "nics" field is matched via its supertype.
+        builder.registerTypeAdapter(DiskTO.class, diskAdaptor);
+        builder.registerTypeHierarchyAdapter(NetworkTO.class, netAdaptor);
+        Gson vmDelegateGson = builder.create();
+        vmAdaptor.initGson(vmDelegateGson);
+
+        // MigrateCommand nests a VirtualMachineTO, so its base Gson needs the 
VirtualMachineTO adapter
+        // (which already renames the nested disks/nics above).
+        builder.registerTypeAdapter(VirtualMachineTO.class, vmAdaptor);
+        Gson migrateDelegateGson = builder.create();
+        migrateAdaptor.initGson(migrateDelegateGson);
+
+        builder.registerTypeAdapter(MigrateCommand.class, migrateAdaptor);
+
         Gson gson = builder.create();
         dsAdaptor.initGson(gson);
         dtAdaptor.initGson(gson);
diff --git a/core/src/test/java/com/cloud/agent/transport/RequestTest.java 
b/core/src/test/java/com/cloud/agent/transport/RequestTest.java
index 0fe42c7cede..d68107e6491 100644
--- a/core/src/test/java/com/cloud/agent/transport/RequestTest.java
+++ b/core/src/test/java/com/cloud/agent/transport/RequestTest.java
@@ -19,14 +19,21 @@
 
 package com.cloud.agent.transport;
 
+import java.lang.reflect.Field;
 import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
 import junit.framework.TestCase;
 
+import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.config.Configurator;
 import org.junit.Assert;
 import org.mockito.Mockito;
 
+import com.google.gson.Gson;
+
 import org.apache.cloudstack.storage.command.DownloadCommand;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 
@@ -35,19 +42,28 @@ import com.cloud.agent.api.BadCommand;
 import com.cloud.agent.api.Command;
 import com.cloud.agent.api.GetHostStatsCommand;
 import com.cloud.agent.api.GetVolumeStatsCommand;
+import com.cloud.agent.api.MigrateCommand;
 import com.cloud.agent.api.SecStorageFirewallCfgCommand;
+import com.cloud.agent.api.StartCommand;
 import com.cloud.agent.api.UpdateHostPasswordCommand;
 import com.cloud.agent.api.storage.DownloadAnswer;
 import com.cloud.agent.api.storage.ListTemplateCommand;
+import com.cloud.agent.api.to.DiskTO;
 import com.cloud.agent.api.to.NfsTO;
+import com.cloud.agent.api.to.NicTO;
+import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.agent.transport.Request.Version;
 import com.cloud.exception.UnsupportedVersionException;
+import com.cloud.host.Host;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.serializer.GsonHelper;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Storage.ImageFormat;
 import com.cloud.storage.Storage.TemplateType;
 import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
 import com.cloud.template.VirtualMachineTemplate;
+import com.cloud.template.VirtualMachineTemplate.BootloaderType;
+import com.cloud.vm.VirtualMachine;
 
 /**
  *
@@ -57,7 +73,28 @@ import com.cloud.template.VirtualMachineTemplate;
  */
 
 public class RequestTest extends TestCase {
-    protected Logger logger = LogManager.getLogger(getClass());
+    private static final Logger logger = 
LogManager.getLogger(RequestTest.class);
+
+    /**
+     * Changes GsonHelper's logging Gson level AND clears its cached 
TypeAdapters.
+     *
+     * Gson bakes each Command class's shouldSkipClass/shouldSkipField 
decision into a TypeAdapter
+     * the first time that class is serialized, then caches it 
(Gson#typeTokenCache) for the life
+     * of the Gson instance. Since GsonHelper.getGsonLogger() is a static 
singleton shared for the
+     * whole test JVM, just calling Configurator.setLevel() again has no 
effect on classes already
+     * cached - they'd keep reflecting whichever level was active the first 
time they were logged.
+     * Clearing the cache after every level change forces a fresh (correct) 
evaluation.
+     */
+    private static void setGsonLoggerLevel(String loggerName, Level level) {
+        Configurator.setLevel(loggerName, level);
+        try {
+            Field field = Gson.class.getDeclaredField("typeTokenCache");
+            field.setAccessible(true);
+            ((Map<?, ?>)field.get(GsonHelper.getGsonLogger())).clear();
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
 
     public void testSerDeser() {
         logger.info("Testing serializing and deserializing works as expected");
@@ -73,6 +110,32 @@ public class RequestTest extends TestCase {
         Request sreq = new Request(2, 3, new Command[] {cmd1, cmd2, cmd3}, 
true, true);
         sreq.setSequence(892403717);
 
+        Logger gsonLogger = LogManager.getLogger(GsonHelper.class);
+        Level level = gsonLogger.getLevel();
+
+        setGsonLoggerLevel(gsonLogger.getName(), Level.DEBUG);
+        String log = sreq.log("Debug", true, Level.DEBUG);
+        assert (log.contains(UpdateHostPasswordCommand.class.getSimpleName()));
+        assert 
(log.contains(SecStorageFirewallCfgCommand.class.getSimpleName()));
+        assert (!log.contains(GetHostStatsCommand.class.getSimpleName()));
+        assert (!log.contains("username"));
+        assert (!log.contains("password"));
+
+        setGsonLoggerLevel(gsonLogger.getName(), Level.TRACE);
+        log = sreq.log("Trace", true, Level.TRACE);
+        System.out.println(log);
+        assert (log.contains(UpdateHostPasswordCommand.class.getSimpleName()));
+        assert 
(log.contains(SecStorageFirewallCfgCommand.class.getSimpleName()));
+        assert (log.contains(GetHostStatsCommand.class.getSimpleName()));
+        assert (!log.contains("username"));
+        assert (!log.contains("password"));
+
+        setGsonLoggerLevel(gsonLogger.getName(), Level.INFO);
+        log = sreq.log("Info", true, Level.INFO);
+        assert (log == null);
+
+        setGsonLoggerLevel(GsonHelper.class.getName(), level);
+
         byte[] bytes = sreq.getBytes();
 
         assert Request.getSequence(bytes) == 892403717;
@@ -186,6 +249,79 @@ public class RequestTest extends TestCase {
         }
     }
 
+    public void testLogging() {
+        logger.info("Testing Logging");
+        GetHostStatsCommand cmd3 = new GetHostStatsCommand("hostguid", 
"hostname", 101);
+        Request sreq = new Request(2, 3, new Command[] {cmd3}, true, true);
+        sreq.setSequence(1);
+        Logger gsonLogger = LogManager.getLogger(GsonHelper.class);
+        Level level = gsonLogger.getLevel();
+
+        setGsonLoggerLevel(GsonHelper.class.getName(), Level.DEBUG);
+        String log = sreq.log("Debug", true, Level.DEBUG);
+        assert (log == null);
+
+        log = sreq.log("Debug", false, Level.DEBUG);
+        assert (log != null);
+
+        setGsonLoggerLevel(GsonHelper.class.getName(), Level.TRACE);
+        log = sreq.log("Trace", true, Level.TRACE);
+        assert (log != null);
+
+        assert (log.contains(GetHostStatsCommand.class.getSimpleName()));
+        logger.debug(log);
+
+        setGsonLoggerLevel(GsonHelper.class.getName(), level);
+    }
+
+    public void testCompatFieldRenamingNestedTOs() {
+        logger.info("Testing that renamed fields are restored on nested TOs 
too, for backward compatibility with older Agents");
+
+        DiskTO diskTO = new DiskTO();
+        diskTO.setDetails(new HashMap<String, String>());
+
+        NicTO nicTO = new NicTO();
+        nicTO.setSecurityGroupEnabled(true);
+
+        VirtualMachineTO vmTO = new VirtualMachineTO(1, "i-2-3-VM", 
VirtualMachine.Type.User, 1, 512, 512L * 1024 * 1024, 512L * 1024 * 1024,
+                BootloaderType.HVM, "Other PV (64-bit)", true, true, 
"vncpassword123");
+        vmTO.setDetails(new HashMap<String, String>());
+        vmTO.setDisks(new DiskTO[] {diskTO});
+        vmTO.setNics(new NicTO[] {nicTO});
+
+        Host host = Mockito.mock(Host.class);
+        Mockito.when(host.getPrivateIpAddress()).thenReturn("10.1.1.1");
+        StartCommand startCmd = new StartCommand(vmTO, host, false);
+
+        Request startReq = new Request(1, 1, startCmd, true);
+        String startWireJson = GsonHelper.getGson().toJson(new Command[] 
{startCmd});
+        assert startWireJson.contains("\"params\"") : 
"VirtualMachineTO.details should be serialized under its old name 'params'";
+        assert startWireJson.contains("\"_details\"") : "nested DiskTO.details 
should be serialized under its old name '_details'";
+        assert startWireJson.contains("\"isSecurityGroupEnabled\"") : "nested 
NicTO.securityGroupEnabled should be serialized under its old name 
'isSecurityGroupEnabled'";
+        assert startWireJson.contains("vncpassword123") : "wire serialization 
should still contain the real vncPassword value";
+
+        Logger gsonLogger = LogManager.getLogger(GsonHelper.class);
+        Level gsonLoggerLevel = gsonLogger.getLevel();
+        setGsonLoggerLevel(GsonHelper.class.getName(), Level.TRACE);
+        String startLogJson;
+        try {
+            startLogJson = startReq.log("Trace", true, Level.TRACE);
+        } finally {
+            setGsonLoggerLevel(GsonHelper.class.getName(), gsonLoggerLevel);
+        }
+        assert startLogJson.contains("\"isSecurityGroupEnabled\"") : "renamed 
fields should still show up in the logging serialization";
+        assert !startLogJson.contains("vncpassword123") : "logging 
serialization should never contain the plaintext vncPassword value";
+
+        MigrateCommand migrateCmd = new MigrateCommand("i-2-3-VM", "10.1.1.2", 
true, vmTO, false);
+        String migrateWireJson = GsonHelper.getGson().toJson(new Command[] 
{migrateCmd});
+        assert migrateWireJson.contains("\"destIp\"") : 
"MigrateCommand.destinationIp should be serialized under its old name 'destIp'";
+        assert migrateWireJson.contains("\"isWindows\"") : 
"MigrateCommand.windows should be serialized under its old name 'isWindows'";
+        assert migrateWireJson.contains("\"vmTO\"") : 
"MigrateCommand.virtualMachine should be serialized under its old name 'vmTO'";
+        assert migrateWireJson.contains("\"params\"") : "VirtualMachineTO 
nested in MigrateCommand should still be renamed";
+        assert migrateWireJson.contains("\"_details\"") : "DiskTO nested 
inside the VirtualMachineTO nested in MigrateCommand should still be renamed";
+        assert migrateWireJson.contains("\"isSecurityGroupEnabled\"") : "NicTO 
nested inside the VirtualMachineTO nested in MigrateCommand should still be 
renamed";
+    }
+
     protected void compareRequest(Request req1, Request req2) {
         assert req1.getSequence() == req2.getSequence();
         assert req1.getAgentId() == req2.getAgentId();

Reply via email to