1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import javax.naming.Context;
  25 import javax.naming.NamingException;
  26 import javax.naming.directory.Attributes;
  27 import java.io.PrintStream;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.nio.file.Paths;
  31 import java.util.Hashtable;
  32 
  33 /**
  34  * This is utility class for DNS test, it contains many helper methods to
  35  * support test construction.
  36  *
  37  * For basic test sequence see TestBase and DNSTestBase
  38  */
  39 public class DNSTestUtils {
  40     public static final String TEST_DNS_SERVER_THREAD = "test.dns.server.thread";
  41     public static final String TEST_DNS_ROOT_URL = "test.dns.root.url";
  42     public static final int HOSTS_LOOKUP_MAX_DEPTH = 3;
  43 
  44     protected static boolean debug = true;
  45 
  46     /**
  47      * Check that attributes contains the mandatory attributes and the right
  48      * objectclass attribute
  49      *
  50      * @param attrs     given attributes to verify
  51      * @param mandatory given mandatory for verification
  52      * @param optional  given optional for verification
  53      * @return <tt>true</tt>  if check ok
  54      */
  55     public static boolean checkSchema(Attributes attrs, String[] mandatory,
  56             String[] optional) {
  57         // Check mandatory attributes
  58         for (String mandatoryAttr : mandatory) {
  59             if (attrs.get(mandatoryAttr) == null) {
  60                 debug("missing mandatory attribute: " + mandatoryAttr);
  61                 return false;
  62             }
  63         }
  64 
  65         // Check optional attributes
  66         int optMissing = 0;
  67         for (String optionalAttr : optional) {
  68             if (attrs.get(optionalAttr) == null) {
  69                 debug("warning: missing optional attribute: " + optionalAttr);
  70                 ++optMissing;
  71             }
  72         }
  73 
  74         if (attrs.size() > (mandatory.length + (optional.length
  75                 - optMissing))) {
  76             debug("too many attributes: " + attrs);
  77             return false;
  78         }
  79 
  80         return true;
  81     }
  82 
  83     /**
  84      * Process command line arguments and init env
  85      * This method will prepare default environments which to be used to initial
  86      * DirContext
  87      *
  88      * @param localServer <tt>true</tt> if this test will run against with local
  89      *                    server against dump message playback
  90      * @param testname    test case name to identify playback file
  91      * @param args        additional arguments for env preparation
  92      * @return prepared env which will be used later to initial DirContext
  93      */
  94     public static Hashtable<Object, Object> initEnv(boolean localServer,
  95             String testname, String[] args) {
  96 
  97         Hashtable<Object, Object> env = new Hashtable<>();
  98 
  99         // set some default parameters if no additional specified
 100         env.put("DNS_DOMAIN", "domain1.com.");
 101         env.put("FOREIGN_DOMAIN", "Central.Sun.COM.");
 102         env.put("FOREIGN_LEAF", "sunweb");
 103 
 104         // set defaults for some JNDI properties
 105         env.put(Context.INITIAL_CONTEXT_FACTORY,
 106                 "com.sun.jndi.dns.DnsContextFactory");
 107 
 108         boolean traceEnable = false;
 109         boolean loopPlayback = false;
 110         for (String arg : args) {
 111             if (arg.startsWith("-D")) {
 112                 extractProperty(arg.substring(2), env);
 113             } else if (arg.equalsIgnoreCase("-trace")) {
 114                 traceEnable = true;
 115             } else if (arg.equalsIgnoreCase("-loop")) {
 116                 loopPlayback = true;
 117             }
 118         }
 119 
 120         debug = Boolean.valueOf(System.getProperty("debug", "true"));
 121 
 122         if (env.get("DNS_SERVER") != null) {
 123             String port = (String) env.get("DNS_PORT");
 124             String portSuffix = (port == null) ? "" : ":" + port;
 125             String url = "dns://" + env.get("DNS_SERVER") + portSuffix;
 126             env.put(Context.PROVIDER_URL, url);
 127             env.put(Context.PROVIDER_URL, url + "/" + env.get("DNS_DOMAIN"));
 128         }
 129 
 130         Thread inst = null;
 131         if (traceEnable) {
 132             // if trace is enabled, create DNSTracer to dump those message
 133             inst = createDNSTracer(testname, env);
 134         } else {
 135             if (localServer) {
 136                 // if use local server, create local DNSServer for playback
 137                 inst = createDNSServer(testname, loopPlayback);
 138             } else {
 139                 // for tests which run against remote server
 140                 // or no server required
 141                 debug("Skip local DNS Server creation ");
 142             }
 143         }
 144 
 145         if (inst != null) {
 146             inst.start();
 147             env.put(TEST_DNS_SERVER_THREAD, inst);
 148             String url = "dns://localhost:" + ((Server) inst).getPort();
 149 
 150             env.put(TEST_DNS_ROOT_URL, url);
 151             env.put(Context.PROVIDER_URL, url + "/" + env.get("DNS_DOMAIN"));
 152         }
 153 
 154         return env;
 155     }
 156 
 157     /**
 158      * Clean-up the given directory context.
 159      *
 160      * @param ctx given context object
 161      */
 162     public static void cleanup(Context ctx) {
 163         if (ctx != null) {
 164             try {
 165                 ctx.close();
 166             } catch (NamingException e) {
 167                 // ignore
 168             }
 169         }
 170     }
 171 
 172     private static void extractProperty(String propString,
 173             Hashtable<Object, Object> env) {
 174         int index;
 175 
 176         if ((index = propString.indexOf('=')) > 0) {
 177             env.put(propString.substring(0, index),
 178                     propString.substring(index + 1));
 179         } else {
 180             throw new RuntimeException(
 181                     "Failed to extract test args property from " + propString);
 182         }
 183     }
 184 
 185     /**
 186      * Return new created DNS tracer
 187      *
 188      * @param testname test case name to identify playback file
 189      * @param env      given env for initialization
 190      * @return created DNS tracer
 191      * @see DNSTracer
 192      */
 193     public static DNSTracer createDNSTracer(String testname,
 194             Hashtable<Object, Object> env) {
 195         try {
 196             PrintStream outStream = new PrintStream(getCaptureFile(testname));
 197             return new DNSTracer(outStream, (String) env.get("DNS_SERVER"),
 198                     Integer.parseInt((String) env.get("DNS_PORT")));
 199         } catch (Exception e) {
 200             throw new RuntimeException(
 201                     "Error: failed to create DNSTracer : " + e.getMessage(), e);
 202         }
 203     }
 204 
 205     /**
 206      * Return new created local DNS Server
 207      *
 208      * @param testname test case name to identify playback file
 209      * @param loop     <tt>true</tt> if DNS server required playback message in loop
 210      * @return created local DNS Server
 211      * @see DNSServer
 212      */
 213     public static DNSServer createDNSServer(String testname, boolean loop) {
 214         String path = getCaptureFile(testname);
 215         if (Files.exists(Paths.get(path))) {
 216             try {
 217                 return new DNSServer(path, loop);
 218             } catch (Exception e) {
 219                 throw new RuntimeException(
 220                         "Error: failed to create DNSServer : " + e.getMessage(),
 221                         e);
 222             }
 223         } else {
 224             throw new RuntimeException(
 225                     "Error: failed to create DNSServer, not found dns "
 226                             + "cache file " + path);
 227         }
 228     }
 229 
 230     /**
 231      * Return dns message capture file path
 232      *
 233      * @param testname test case name to identify playback file
 234      * @return capture file path
 235      */
 236     public static String getCaptureFile(String testname) {
 237         return Paths.get(System.getProperty("test.src"))
 238                 .resolve(testname + ".dns").toString();
 239     }
 240 
 241     /**
 242      * Enable hosts file
 243      *
 244      * @param hostsFile given hosts file
 245      */
 246     public static void enableHostsFile(String hostsFile) {
 247         System.out.println("Enable jdk.net.hosts.file = " + hostsFile);
 248         System.setProperty("jdk.net.hosts.file", hostsFile);
 249     }
 250 
 251     /**
 252      * Enable hosts file by given searching depth
 253      *
 254      * @param depth given depth for searching hosts file
 255      */
 256     public static void enableHostsFile(int depth) {
 257         Path path = Paths.get(System.getProperty("test.src", "."))
 258                 .toAbsolutePath();
 259         for (int i = depth; i >= 0; i--) {
 260             Path filePath = path.resolve("hosts");
 261             if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
 262                 enableHostsFile(filePath.toString());
 263                 break;
 264             }
 265 
 266             path = path.getParent();
 267             if (path == null) {
 268                 break;
 269             }
 270         }
 271     }
 272 
 273     /**
 274      * Print given object if debug enabled
 275      *
 276      * @param object given object to print
 277      */
 278     public static void debug(Object object) {
 279         if (debug) {
 280             System.out.println(object);
 281         }
 282     }
 283 
 284     /**
 285      * Verify attributes contains the mandatory attributes and the right
 286      * objectclass attribute, will throw RuntimeException if verify failed
 287      *
 288      * @param attrs     given attributes to verify
 289      * @param mandatory given mandatory for verification
 290      * @param optional  given optional for verification
 291      */
 292     public static void verifySchema(Attributes attrs, String[] mandatory,
 293             String[] optional) {
 294         debug(attrs);
 295         if (!checkSchema(attrs, mandatory, optional)) {
 296             throw new RuntimeException("Check schema failed.");
 297         }
 298     }
 299 
 300     /**
 301      * Return dns root url
 302      *
 303      * @param env given env
 304      * @return dns root url
 305      */
 306     public static String getRootUrl(Hashtable<Object, Object> env) {
 307         return (String) env.get(TEST_DNS_ROOT_URL);
 308     }
 309 
 310     /**
 311      * Assemble a fully-qualified domain name from the base component and the
 312      * domain name.
 313      *
 314      * @param base    given base component
 315      * @param env     given env
 316      * @param primary <tt>true</tt> if primary domain
 317      * @return assembled fully-qualified domain name
 318      */
 319     public static String buildFqdn(String base, Hashtable<Object, Object> env,
 320             boolean primary) {
 321         String domain = (String) (primary ?
 322                 env.get("DNS_DOMAIN") :
 323                 env.get("FOREIGN_DOMAIN"));
 324 
 325         return base + "." + domain;
 326     }
 327 }