aboutsummaryrefslogtreecommitdiffstats
path: root/feature-server-pool/src/test/java/org/onap/policy/drools/serverpooltest/Adapter.java
blob: 22404605f380dc650fc364dc679f877c5e1f0122 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/*
 * ============LICENSE_START=======================================================
 * feature-server-pool
 * ================================================================================
 * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
 * ================================================================================
 * Licensed 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.
 * ============LICENSE_END=========================================================
 */

package org.onap.policy.drools.serverpooltest;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.LinkedBlockingQueue;
import org.kie.api.runtime.KieSession;
import org.onap.policy.common.utils.network.NetworkUtil;
import org.onap.policy.drools.serverpool.Util;
import org.onap.policy.drools.utils.PropertyUtil;

/**
 * This is a common base class for 6 'AdapterImpl' instances, all running
 * with their own copies of the server pool classes, and a number of the ONAP
 * classes. The purpose is to simulate 6 separate hosts in a server pool.
 * Note that there is potentially a 7th copy of any of these classes, which is
 * the one loaded with the system class loader. Ideally, those classes
 * shouldn't be referred to, but there were some problems during testing,
 * where they unexpectedly were (prompting a change in
 * 'ExtendedObjectInputStream'). This is referred to as the 'null' host,
 * where the classes may exist, but have not gone through initialization.
 */
public abstract class Adapter {
    // 'true' indicates that initialization is still needed
    private static boolean initNeeded = true;
    /*
     * Each 'Adapter' instance is implemented by 'AdapterImpl', but loaded
     * with a different class loader that provides each with a different copy
     * of the set of classes with packages in the list below (see references to
     * 'BlockingClassLoader').
     */
    public static Adapter[] adapters = new Adapter[6];

    /**
     * Ensure that all adapters have been initialized.
     */
    public static void ensureInit() throws Exception {
        synchronized (Adapter.class) {
            if (initNeeded) {
                initNeeded = false;

                // start DMAAP Simulator
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        SimDmaap.start();
                    }
                }, "DMAAP Simulator").start();

                // wait 200 millisecond to allow time for the port 3904 listener
                final String propertyFile = "src/test/resources/feature-server-pool-test.properties";
                Properties prop = PropertyUtil.getProperties(propertyFile);
                assertTrue(NetworkUtil.isTcpPortOpen(prop.getProperty("server.pool.discovery.servers"),
                    Integer.parseInt(prop.getProperty("server.pool.discovery.port")), 250, 200));

                // build 'BlockingClassLoader'
                BlockingClassLoader bcl = new BlockingClassLoader(
                    Adapter.class.getClassLoader(),
                    // All 'org.onap.policy.*' classes are adapter-specific, except
                    // for the exclusions listed below.
                    "org.onap.policy.*"
                );
                bcl.addExclude("org.onap.policy.drools.core.DroolsRunnable");
                bcl.addExclude("org.onap.policy.drools.serverpooltest.*");

                // build URL list for class loader
                URL[] urls = {};

                // iterate through 'adapter' entries
                ClassLoader saveClassLoader =
                    Thread.currentThread().getContextClassLoader();
                if (saveClassLoader instanceof URLClassLoader) {
                    urls = ((URLClassLoader) saveClassLoader).getURLs();
                } else {
                    // the parent is not a 'URLClassLoader' --
                    // try to get this information from 'java.class.path'
                    ArrayList<URL> tmpUrls = new ArrayList<>();
                    for (String entry : System.getProperty("java.class.path").split(
                        File.pathSeparator)) {
                        if (new File(entry).isDirectory()) {
                            tmpUrls.add(new URL("file:" + entry + "/"));
                        } else {
                            tmpUrls.add(new URL("file:" + entry));
                        }
                    }
                    urls = tmpUrls.toArray(new URL[0]);
                }
                try {
                    for (int i = 0; i < adapters.length; i += 1) {
                        /*
                         * Build a new 'ClassLoader' for this adapter. The
                         * 'ClassLoader' hierarchy is:
                         *
                         * AdapterClassLoader(one copy per Adapter) ->
                         * BlockingClassLoader ->
                         * base ClassLoader (with the complete URL list)
                         */
                        ClassLoader classLoader =
                            new AdapterClassLoader(i, urls, bcl);
                        /*
                         * set the current thread class loader, which should be
                         * inherited by any child threads created during
                         * the initialization of the adapter
                         */
                        Thread.currentThread().setContextClassLoader(classLoader);

                        // now, build the adapter -- it is not just a new instance,
                        // but a new copy of class 'AdapterImpl'
                        Adapter adapter = (Adapter) classLoader.loadClass(
                            "org.onap.policy.drools.serverpool.AdapterImpl")
                            .newInstance();

                        // initialize the adapter
                        adapter.init(i);
                        adapters[i] = adapter;
                    }
                } finally {
                    // restore the class loader to that used during the Junit tests
                    Thread.currentThread().setContextClassLoader(saveClassLoader);
                }
            }
        }
    }

    /**
     * Shut everything down.
     */
    public static void ensureShutdown() {
        for (Adapter adapter : adapters) {
            adapter.shutdown();
        }
        SimDmaap.stop();
        // not sure why the following is started
        Util.shutdown();
    }

    /**
     * Runs server pool initialization for a particular host.
     *
     * @param index the index of the adapter (0-5)
     */
    public abstract void init(int index) throws Exception;

    /**
     * Shuts down the server pool for this host.
     */
    public abstract void shutdown();

    /**
     * Return a 'LinkedBlockingQueue' instance, which is used as a way for
     *     Drools code to signal back to running Junit tests.
     *
     * @return a 'LinkedBlockingQueue' instance, which is used as a way for
     *     Drools code to signal back to running Junit tests
     */
    public abstract LinkedBlockingQueue<String> notificationQueue();

    /**
     * This method blocks and waits for all buckets to have owners, or for
     * a timeout, whichever occurs first.
     *
     * @param endTime the point at which timeout occurs
     * @return 'true' if all buckets have owners, 'false' if a timeout occurred
     */
    public abstract boolean waitForInit(long endTime) throws InterruptedException;

    /**
     * Return an object providing indirect references to a select set of
     *     static 'Server' methods.
     *
     * @return an object providing indirect references to a select set of
     *     static 'Server' methods
     */
    public abstract ServerWrapper.Static getServerStatic();

    /**
     * Return an object providing an indirect reference to the lead 'Server'
     *     object.
     *
     * @return an object providing an indirect reference to the lead 'Server'
     *     object
     */
    public abstract ServerWrapper getLeader();

    /**
     * Return an object providing indirect references to a select set of
     *     static 'Bucket' methods.
     *
     * @return an object providing indirect references to a select set of
     *     static 'Bucket' methods
     */
    public abstract BucketWrapper.Static getBucketStatic();

    /**
     * Create a new 'TargetLock' instance, returning an indirect reference.
     *
     * @param key string key identifying the lock
     * @param ownerKey string key identifying the owner, which must hash to
     *     a bucket owned by the current host (it is typically a 'RequestID')
     * @param owner owner of the lock (will be notified when going from
     *     WAITING to ACTIVE)
     * @param waitForLock this controls the behavior when 'key' is already
     *     locked - 'true' means wait for it to be freed, 'false' means fail
     */
    public abstract TargetLockWrapper newTargetLock(
        String key, String ownerKey, TargetLockWrapper.Owner owner,
        boolean waitForLock);

    /**
     * Create a new 'TargetLock' instance, returning an indirect reference.
     *
     * @param key string key identifying the lock
     * @param ownerKey string key identifying the owner, which must hash to
     *     a bucket owned by the current host (it is typically a 'RequestID')
     * @param owner owner of the lock (will be notified when going from
     *     WAITING to ACTIVE)
     */
    public abstract TargetLockWrapper newTargetLock(
        String key, String ownerKey, TargetLockWrapper.Owner owner);

    /**
     * Call 'TargetLock.DumpLocks.dumpLocks'
     *
     * @param out where the output should be displayed
     * @param detail 'true' provides additional bucket and host information
     *     (but abbreviates all UUIDs in order to avoid excessive
     *     line length)
     */
    public abstract void dumpLocks(PrintStream out, boolean detail);

    /**
     * Create and initialize PolicyController 'TestController', and start
     * the associated Drools container and session.
     *
     * @return a string containing controller session information
     */
    public abstract String createController();

    /**
     * Send an event in the form of a JSON message string. The message is
     * sent to JUNIT-TEST-TOPIC, and the JSON object is converted to a
     * 'TestDroolsObject' (all compatible with the Drools session created by
     * 'createController'.
     *
     * @param key determines the bucket number, which affects which host the
     *     message is eventually routed to
     */
    public abstract void sendEvent(String key);

    /**
     * Return the one-and-only 'KieSession' on this host.
     *
     * @return the one-and-only 'KieSession' on this host
     */
    public abstract KieSession getKieSession();

    /**
     * Insert an object into the one-and-only Drools session.
     *
     * @param object the object to insert
     */
    public abstract void insertDrools(Object object);

    // some test utilities

    /**
     * Determine whether any of the objects passed as parameters are of a class
     * that belongs to different adapter. Print messages are displayed
     * for any that do occur.
     *
     * @param objects one or more objects to be tested
     * @return 'true' if one or more are foreign
     */
    public abstract boolean isForeign(Object... objects);

    /**
     * This method is used to generate keys that hash to a bucket associated
     * with a particular server. The algorithm generates a key using 'prefix'
     * concatenated with a numeric value, and searches for the first one on
     * the desired host. It will try up to 10000 indices before giving up --
     * each host owns 1/6 of the buckets, should the 10000 number should be
     * way more than enough. The tests are written with the assumption that
     * a valid key will be returned, and 'NullPointerException' is an acceptable
     * way to handle the situation if this doesn't work out somehow.
     *
     * @param prefix the first portion of the key
     * @param startingIndex the first index to try
     * @param host this indicates the 'Server' instance to locate, which must
     *     not be foreign to this adapter
     * @return a key associated with 'host' ('null' if not found)
     */
    public abstract String findKey(String prefix, int startingIndex, ServerWrapper host);

    /**
     * Equivalent to 'findKey(prefix, startingIndex, THIS-SERVER)'.
     *
     * @param prefix the first portion of the key
     * @param startingIndex the first index to try
     * @return a key associated with 'host' ('null' if not found)
     */
    public abstract String findKey(String prefix, int startingIndex);

    /**
     * Equivalent to 'findKey(prefix, 1, THIS-SERVER)'.
     *
     * @param prefix the first portion of the key
     * @return a key associated with 'host' ('null' if not found)
     */
    public abstract String findKey(String prefix);

    /* ============================================================ */

    /**
     * This class is basically a 'URLClassLoader', but with a 'toString()'
     * method that indicates the host and adapter number.
     */
    public static class AdapterClassLoader extends URLClassLoader {
        private int index;

        public AdapterClassLoader(int index, URL[] urls, ClassLoader parent) {
            super(urls, parent);
            this.index = index;
        }

        @Override
        public String toString() {
            return "AdapterClassLoader(" + index + ")";
        }
    }
}