Files
123123/ZA.CoreService.ESBCertificateManager/Tools/SonicMfContainerTool.java
T
GizzlerandCursor 7a9cdb6ecf Force new MfApi tool build and MBean stop restart path.
Reject stale SonicMfContainerTool.class, print ToolVersion, prefer tool on classpath.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:22:39 +02:00

717 lines
24 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanInfo;
import javax.management.ObjectName;
/**
* Sonic Management Application API helper (same restart intent as SMC).
*
* TOOL_VERSION is printed first so we can verify the deployed build.
*
* Remote JMSConnectorClient is "unbounded". IAgentProxy.restart() often throws
* "Operation unsupported for unbounded client connector". SMC Restart typically
* stops the agent via JMX; Launch Daemon brings it back.
*
* Compile: javac --release 8 SonicMfContainerTool.java
*/
public final class SonicMfContainerTool
{
/** Bump when restart logic changes must appear in app output. */
public static final String TOOL_VERSION = "2026-07-24c-mbean-stop";
public static void main(String[] args)
{
info("ToolVersion=" + TOOL_VERSION);
try
{
Args a = Args.parse(args);
if (a.command == null)
{
fail("Usage: SonicMfContainerTool ping|list|restart --domain D --url U --user U [--password P] [--container C] [--timeout SEC]");
return;
}
if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user))
{
fail("domain, url und user sind Pflicht.");
return;
}
if ("restart".equals(a.command) && isBlank(a.container))
{
fail("restart erfordert --container.");
return;
}
long timeoutMs = Math.max(5_000L, a.timeoutSec * 1000L);
Object connector = connect(a.url, a.user, a.password, timeoutMs);
try
{
if ("ping".equals(a.command))
{
ping(connector, a.domain);
}
else if ("list".equals(a.command))
{
list(connector, a.domain);
}
else if ("restart".equals(a.command))
{
restart(connector, a.domain, a.container);
}
else
{
fail("Unbekannter Befehl: " + a.command);
}
}
finally
{
try
{
connector.getClass().getMethod("disconnect").invoke(connector);
}
catch (Exception ignore)
{
/* ignore */
}
}
}
catch (Throwable t)
{
fail(rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage());
}
}
private static Object connect(String url, String user, String password, long timeoutMs)
throws Exception
{
Hashtable env = new Hashtable();
env.put("ConnectionURLs", url);
env.put("DefaultUser", user);
env.put("DefaultPassword", password == null ? "" : password);
Class addressCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorAddress");
Object address = addressCl.getConstructor(new Class[] { Hashtable.class }).newInstance(new Object[] { env });
Class clientCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient");
Object connector = clientCl.getConstructor(new Class[] {}).newInstance(new Object[] {});
// Optional: Request-Timeout setzen (falls vorhanden)
trySetTimeout(connector, timeoutMs);
Method connect = findConnect(clientCl);
if (connect == null)
{
fail("JMSConnectorClient.connect(...) nicht gefunden.");
return null;
}
Class[] pts = connect.getParameterTypes();
Object[] callArgs;
if (pts.length == 2)
{
callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) };
}
else if (pts.length == 1)
{
callArgs = new Object[] { address };
}
else
{
callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) };
}
connect.invoke(connector, callArgs);
info("Connected url=" + url + " user=" + user);
return connector;
}
private static Method findConnect(Class clientCl)
{
Method best = null;
Method[] methods = clientCl.getMethods();
for (int i = 0; i < methods.length; i++)
{
Method m = methods[i];
if (!"connect".equals(m.getName()))
{
continue;
}
if (m.getParameterTypes().length == 2)
{
return m;
}
if (m.getParameterTypes().length == 1)
{
best = m;
}
}
return best;
}
private static void trySetTimeout(Object connector, long timeoutMs)
{
String[] names = new String[] { "setTimeout", "setRequestTimeout", "setDefaultTimeout" };
for (int i = 0; i < names.length; i++)
{
try
{
Method m = connector.getClass().getMethod(names[i], new Class[] { Long.TYPE });
m.invoke(connector, new Object[] { Long.valueOf(timeoutMs) });
info("Set " + names[i] + "=" + timeoutMs);
return;
}
catch (NoSuchMethodException ignore)
{
try
{
Method m = connector.getClass().getMethod(names[i], new Class[] { Integer.TYPE });
m.invoke(connector, new Object[] { Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)) });
info("Set " + names[i] + "=" + timeoutMs);
return;
}
catch (Exception ignore2)
{
/* next */
}
}
catch (Exception ignore)
{
/* next */
}
}
}
private static Object boxTimeout(Class type, long timeoutMs)
{
if (type == Long.TYPE || type == Long.class)
{
return Long.valueOf(timeoutMs);
}
if (type == Integer.TYPE || type == Integer.class)
{
return Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs));
}
return Long.valueOf(timeoutMs);
}
private static void ping(Object connector, String domain) throws Exception
{
Set names = queryAgents(connector);
int matched = 0;
if (names != null)
{
for (Iterator it = names.iterator(); it.hasNext(); )
{
ObjectName on = (ObjectName) it.next();
if (extractContainer(on, domain) != null)
{
matched++;
}
}
}
System.out.println("OK:MfApiPing domain=" + domain + " agents=" + matched);
System.out.flush();
}
private static void list(Object connector, String domain) throws Exception
{
Set names = queryAgents(connector);
int count = 0;
if (names != null)
{
for (Iterator it = names.iterator(); it.hasNext(); )
{
ObjectName on = (ObjectName) it.next();
String container = extractContainer(on, domain);
if (container != null)
{
System.out.println(container);
count++;
}
}
}
if (count == 0)
{
info("KeineAgentContainer Domain=" + domain);
}
System.out.println("OK:List count=" + count);
System.out.flush();
}
private static void restart(Object connector, String domain, String container) throws Exception
{
String shortName = shortContainer(container);
// Zuerst Preferred-ObjectName OHNE queryNames query kann schon scheitern/irreführen
ObjectName preferred = new ObjectName(domain + "." + shortName + ":ID=AGENT");
info("PreferredObjectName=" + preferred);
ObjectName on = preferred;
try
{
ObjectName resolved = resolveAgentObjectName(connector, domain, shortName);
if (resolved != null)
{
on = resolved;
}
}
catch (Throwable t)
{
warn("resolveAgentObjectName: " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage()
+ " nutze PreferredObjectName");
}
info("ObjectName=" + on);
dumpOpsSafe(connector, on);
StringBuffer attempts = new StringBuffer();
// 1) JMX invoke stop/restart/shutdown (remote / wie SMC)
if (tryMBeanLifecycle(connector, on, shortName, domain, attempts))
{
return;
}
// 2) IAgentProxy stop/restart
if (tryAgentProxyLifecycle(connector, on, shortName, domain, attempts))
{
return;
}
// 3) Manager-MBeans mit Container-Namen
if (tryManagerContainerOps(connector, domain, shortName, attempts))
{
return;
}
fail("Kein Neustart-Weg erfolgreich. ToolVersion=" + TOOL_VERSION + "\nAttempts:\n" + attempts.toString());
}
private static boolean tryMBeanLifecycle(
Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts)
{
String[] ops = new String[] { "stop", "restart", "shutdown" };
for (int i = 0; i < ops.length; i++)
{
String op = ops[i];
try
{
info("Trying MBean.invoke(" + op + ") on " + on);
invokeNoArgs(connector, on, op);
okRestart("MBean." + op, shortName, domain);
return true;
}
catch (Throwable t)
{
Throwable root = rootOf(t);
String line = "MBean." + op + " => " + root.getClass().getName() + ": " + root.getMessage();
warn(line);
attempts.append(line).append('\n');
// stop kann die Verbindung reissen wenn Message darauf hindeutet, als Erfolg werten
if ("stop".equals(op) && looksLikeStopSideEffect(root))
{
warn("Treat stop side-effect as success (agent going down)");
okRestart("MBean.stop(side-effect)", shortName, domain);
return true;
}
}
}
return false;
}
private static boolean tryAgentProxyLifecycle(
Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts)
{
try
{
Class factoryCl = Class.forName("com.sonicsw.mf.mgmtapi.runtime.MFProxyFactory");
Method create = factoryCl.getMethod("createAgentProxy", new Class[] {
Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient"),
ObjectName.class
});
Object agent = create.invoke(null, new Object[] { connector, on });
info("StateBefore=" + safeState(agent));
String[] methods = new String[] { "stop", "restart", "shutdown" };
for (int i = 0; i < methods.length; i++)
{
String mName = methods[i];
try
{
info("Trying IAgentProxy." + mName);
agent.getClass().getMethod(mName).invoke(agent);
okRestart("IAgentProxy." + mName, shortName, domain);
return true;
}
catch (Throwable t)
{
Throwable root = rootOf(t);
String line = "IAgentProxy." + mName + " => " + root.getClass().getName() + ": " + root.getMessage();
warn(line);
attempts.append(line).append('\n');
if ("stop".equals(mName) && looksLikeStopSideEffect(root))
{
okRestart("IAgentProxy.stop(side-effect)", shortName, domain);
return true;
}
}
}
}
catch (Throwable t)
{
Throwable root = rootOf(t);
String line = "IAgentProxy path => " + root.getClass().getName() + ": " + root.getMessage();
warn(line);
attempts.append(line).append('\n');
}
return false;
}
private static boolean tryManagerContainerOps(
Object connector, String domain, String shortName, StringBuffer attempts)
{
String[] candidates = new String[] {
domain + ".DomainManager:ID=DomainManager",
domain + ".DomainManager:ID=AGENT",
domain + ".dm:ID=AGENT"
};
for (int i = 0; i < candidates.length; i++)
{
try
{
ObjectName pattern = new ObjectName(candidates[i]);
Set found = queryNames(connector, pattern);
if (found == null || found.isEmpty())
{
continue;
}
ObjectName real = (ObjectName) found.iterator().next();
info("Manager=" + real);
dumpOpsSafe(connector, real);
String[] ops = new String[] { "restartContainer", "stopContainer", "startContainer" };
for (int o = 0; o < ops.length; o++)
{
try
{
info("Trying " + ops[o] + "(\"" + shortName + "\") on " + real);
invokeStringArg(connector, real, ops[o], shortName);
okRestart("Manager." + ops[o], shortName, domain);
return true;
}
catch (Throwable t)
{
Throwable root = rootOf(t);
String line = ops[o] + " => " + root.getClass().getName() + ": " + root.getMessage();
warn(line);
attempts.append(line).append('\n');
}
}
}
catch (Throwable t)
{
warn("Manager candidate " + candidates[i] + ": " + rootOf(t).getMessage());
}
}
// Scan AGENT-MBeans nach Container-Ops
try
{
Set agents = queryAgents(connector);
if (agents == null)
{
return false;
}
for (Iterator it = agents.iterator(); it.hasNext(); )
{
ObjectName on = (ObjectName) it.next();
String jmxDomain = on.getDomain();
if (jmxDomain == null)
{
continue;
}
String lower = jmxDomain.toLowerCase();
if (!(lower.contains("domainmanager") || lower.contains(".dm")
|| lower.contains("collector") || lower.contains("host")
|| lower.contains("launch")))
{
continue;
}
info("Scan manager agent " + on);
dumpOpsSafe(connector, on);
String[] ops = new String[] { "restartContainer", "stopContainer" };
for (int i = 0; i < ops.length; i++)
{
try
{
invokeStringArg(connector, on, ops[i], shortName);
okRestart(ops[i] + "@" + on, shortName, domain);
return true;
}
catch (Throwable t)
{
attempts.append(ops[i]).append('@').append(on).append(" => ")
.append(rootOf(t).getMessage()).append('\n');
}
}
}
}
catch (Throwable t)
{
warn("Manager scan: " + rootOf(t).getMessage());
}
return false;
}
private static boolean looksLikeStopSideEffect(Throwable root)
{
if (root == null || root.getMessage() == null)
{
return false;
}
String m = root.getMessage().toLowerCase();
return m.indexOf("disconnect") >= 0
|| m.indexOf("closed") >= 0
|| m.indexOf("not connected") >= 0
|| m.indexOf("connection lost") >= 0
|| m.indexOf("broker") >= 0 && m.indexOf("down") >= 0;
}
private static void okRestart(String method, String shortName, String domain)
{
System.out.println("OK:RestartInvoked method=" + method
+ " container=" + shortName + " domain=" + domain
+ " tool=" + TOOL_VERSION);
System.out.flush();
}
private static void invokeNoArgs(Object connector, ObjectName on, String op) throws Exception
{
Method invoke = connector.getClass().getMethod("invoke", new Class[] {
ObjectName.class, String.class, Object[].class, String[].class
});
invoke.invoke(connector, new Object[] { on, op, new Object[0], new String[0] });
}
private static void invokeStringArg(Object connector, ObjectName on, String op, String arg) throws Exception
{
Method invoke = connector.getClass().getMethod("invoke", new Class[] {
ObjectName.class, String.class, Object[].class, String[].class
});
invoke.invoke(connector, new Object[] {
on, op, new Object[] { arg }, new String[] { "java.lang.String" }
});
}
private static void dumpOpsSafe(Object connector, ObjectName on)
{
try
{
Method getInfo = connector.getClass().getMethod("getMBeanInfo", new Class[] { ObjectName.class });
MBeanInfo infoBean = (MBeanInfo) getInfo.invoke(connector, new Object[] { on });
if (infoBean == null || infoBean.getOperations() == null)
{
info("Ops(" + on + ")=(null)");
return;
}
StringBuffer sb = new StringBuffer();
MBeanOperationInfo[] ops = infoBean.getOperations();
for (int i = 0; i < ops.length; i++)
{
String name = ops[i].getName();
String lower = name.toLowerCase();
if (lower.indexOf("restart") >= 0 || lower.indexOf("stop") >= 0
|| lower.indexOf("start") >= 0 || lower.indexOf("shutdown") >= 0
|| lower.indexOf("container") >= 0)
{
if (sb.length() > 0)
{
sb.append(',');
}
sb.append(name);
}
}
info("Ops(" + on + ")=" + (sb.length() == 0 ? "(keine lifecycle)" : sb.toString()));
}
catch (Throwable t)
{
warn("getMBeanInfo(" + on + "): " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage());
}
}
private static Set queryAgents(Object connector) throws Exception
{
return queryNames(connector, new ObjectName("*:ID=AGENT"));
}
private static Set queryNames(Object connector, ObjectName pattern) throws Exception
{
Method query = connector.getClass().getMethod("queryNames", new Class[] {
ObjectName.class, javax.management.QueryExp.class
});
return (Set) query.invoke(connector, new Object[] { pattern, null });
}
private static ObjectName resolveAgentObjectName(Object connector, String domain, String container)
throws Exception
{
ObjectName preferred = new ObjectName(domain + "." + container + ":ID=AGENT");
Set exact = queryNames(connector, preferred);
if (exact != null && !exact.isEmpty())
{
return (ObjectName) exact.iterator().next();
}
Set names = queryAgents(connector);
if (names != null)
{
for (Iterator it = names.iterator(); it.hasNext(); )
{
ObjectName on = (ObjectName) it.next();
String c = extractContainer(on, domain);
if (c != null && c.equalsIgnoreCase(container))
{
return on;
}
}
}
return preferred;
}
private static String extractContainer(ObjectName on, String domain)
{
if (on == null || isBlank(domain))
{
return null;
}
String jmxDomain = on.getDomain();
if (jmxDomain == null)
{
return null;
}
String prefix = domain + ".";
if (jmxDomain.regionMatches(true, 0, prefix, 0, prefix.length()))
{
return jmxDomain.substring(prefix.length());
}
return null;
}
private static String shortContainer(String container)
{
if (container == null)
{
return "";
}
int dot = container.indexOf('.');
if (dot >= 0 && dot < container.length() - 1)
{
return container.substring(dot + 1);
}
return container;
}
private static String safeState(Object agent)
{
try
{
Object s = agent.getClass().getMethod("getStateString").invoke(agent);
return s == null ? "?" : String.valueOf(s);
}
catch (Throwable t)
{
return "?";
}
}
private static Throwable rootOf(Throwable t)
{
Throwable root = t;
while (root != null && root.getCause() != null && root.getCause() != root)
{
root = root.getCause();
}
return root == null ? t : root;
}
private static void info(String message)
{
System.out.println("INFO:" + message);
System.out.flush();
}
private static void warn(String message)
{
System.out.println("WARN:" + message);
System.out.flush();
}
private static void fail(String message)
{
System.err.println("ERROR:" + message);
System.err.flush();
System.exit(2);
}
private static boolean isBlank(String s)
{
return s == null || s.trim().length() == 0;
}
private static final class Args
{
String command;
String domain;
String url;
String user;
String password;
String container;
int timeoutSec = 120;
static Args parse(String[] args)
{
Args a = new Args();
if (args == null || args.length == 0)
{
return a;
}
a.command = args[0].toLowerCase();
for (int i = 1; i < args.length; i++)
{
String key = args[i];
if (!key.startsWith("--") || i + 1 >= args.length)
{
continue;
}
String val = args[++i];
if ("--domain".equals(key)) a.domain = val;
else if ("--url".equals(key)) a.url = val;
else if ("--user".equals(key)) a.user = val;
else if ("--password".equals(key)) a.password = val;
else if ("--container".equals(key)) a.container = val;
else if ("--timeout".equals(key))
{
try { a.timeoutSec = Integer.parseInt(val); } catch (Exception ignore) { /* keep */ }
}
}
if (isBlank(a.password))
{
String env = System.getenv("ESB_SONIC_PASSWORD");
if (!isBlank(env))
{
a.password = env;
}
}
return a;
}
}
}