001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2005 Emic Networks.
004: * Contact: sequoia@continuent.org
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * Initial developer(s): Emmanuel Cecchet.
019: * Contributor(s): ______________________.
020: */package org.continuent.sequoia.driver.connectpolicy;
021:
022: import java.io.IOException;
023: import java.net.Socket;
024: import java.util.ArrayList;
025: import java.util.Iterator;
026:
027: import org.continuent.sequoia.common.exceptions.NoMoreControllerException;
028: import org.continuent.sequoia.driver.ControllerInfo;
029: import org.continuent.sequoia.driver.ControllerWatcher;
030: import org.continuent.sequoia.driver.SequoiaUrl;
031: import org.continuent.sequoia.driver.SocketKillerCallBack;
032:
033: /**
034: * This class defines an AbstractControllerConnectPolicy used by the driver to
035: * choose a controller to connect to.
036: *
037: * @author <a href="mailto:emmanuel.cecchet@emicnetworks.com">Emmanuel Cecchet
038: * </a>
039: * @version 1.0
040: */
041: public abstract class AbstractControllerConnectPolicy {
042: /** Holds a controller and its last vdb failure */
043: protected class ControllerAndVdbState {
044: ControllerInfo controller;
045: long lastVdbFailure = 0;
046:
047: /**
048: * Constructs a <code>ControllerAndVdbState</code> object with the given
049: * controller, considering last vdb failure is 0 (ie. no failure)
050: *
051: * @param ctrl controller to refer to
052: */
053: ControllerAndVdbState(ControllerInfo ctrl) {
054: controller = ctrl;
055: }
056:
057: /**
058: * Gives the state of the vdb. Returns true if the last vdb failure is older
059: * than 5 seconds (refering to given time), false if the vdb failed during
060: * the last 5 seconds
061: *
062: * @param now the time to compare last vdb failure to
063: * @return tru is the vdb can be considered as up, false otherwise
064: */
065: boolean isVdbUp(long now) {
066: if ((now - 5000) < lastVdbFailure)
067: return false;
068: return true;
069: }
070:
071: /**
072: * Gives a string representation of this object: prints controller + state
073: */
074: public String toString() {
075: return controller
076: + " ("
077: + (isVdbUp(System.currentTimeMillis()) ? "VDB up)"
078: : "VDB down)");
079: }
080: }
081:
082: /**
083: * Up-to-date list of controllers that respond to pings with associated
084: * lastVdbFailure time stamp
085: */
086: protected ArrayList aliveControllers;
087: /** Controller watcher thread */
088: protected ControllerWatcher watcher;
089: /** Callback for controller failures and come backs */
090: protected SocketKillerCallBack callback;
091: /** Level of logging (will be done on stdout) */
092: protected int debugLevel = SequoiaUrl.DEBUG_LEVEL_OFF;
093:
094: /**
095: * Creates a new <code>AbstractControllerConnectPolicy</code> object
096: *
097: * @param controllerList the controller list on which the policy applies
098: * @param pingDelayInMs Interval in milliseconds between two pings of a
099: * controller
100: * @param controllerTimeoutInMs timeout in milliseconds after which a
101: * controller is considered as dead if it did not respond to pings
102: * @param debugLevel the debug level to use
103: * @see org.continuent.sequoia.driver.SequoiaUrl#DEBUG_LEVEL_OFF
104: */
105: public AbstractControllerConnectPolicy(
106: ControllerInfo[] controllerList, int pingDelayInMs,
107: int controllerTimeoutInMs, int debugLevel) {
108: if (controllerList == null)
109: throw new NullPointerException(
110: "Invalid null controller list in connect policy constructor");
111: if (controllerList.length == 0)
112: throw new RuntimeException(
113: "Invalid empty controller list in connect policy constructor");
114: this .aliveControllers = new ArrayList(controllerList.length);
115: for (int i = 0; i < controllerList.length; i++) {
116: // vdbLastFailure will be initialized at 0, ie. no vdb failure
117: this .aliveControllers.add(new ControllerAndVdbState(
118: controllerList[i]));
119: }
120: this .debugLevel = debugLevel;
121:
122: // Create the objects for controller watcher
123: this .callback = new SocketKillerCallBack(this , debugLevel);
124: try {
125: watcher = new ControllerWatcher(controllerList, callback,
126: pingDelayInMs, controllerTimeoutInMs, debugLevel);
127: watcher.setDaemon(true);
128: watcher.start();
129: } catch (IOException cause) {
130: watcher = null;
131: if (debugLevel >= SequoiaUrl.DEBUG_LEVEL_INFO)
132: System.out
133: .println("Controller watcher creation failed, failover won't work!");
134: }
135: }
136:
137: protected void finalize() throws Throwable {
138: watcher.terminateBoth();
139: super .finalize();
140: }
141:
142: /**
143: * Get a controller using the implementation specific policy
144: *
145: * @return <code>ControllerInfo</code> of the selected controller
146: * @throws NoMoreControllerException if no controller in the controller list
147: * is reachable
148: */
149: public abstract ControllerInfo getController()
150: throws NoMoreControllerException;
151:
152: /**
153: * Registers the given socket to the callback so it can kill this socket when
154: * the controller is detected as failed
155: *
156: * @param controller the controller to which the socket is connected
157: * @param socket the socket to register
158: */
159: public synchronized void registerSocket(ControllerInfo controller,
160: Socket socket) {
161: callback.registerSocket(controller, socket);
162: }
163:
164: /**
165: * Tell the watcher that a failure has been detected on the given controller.<br>
166: * This function should be called when a connection error occurs on the given
167: * controller. We don't update the aliveController list here: the watcher
168: * callback will call controllerDown() for us
169: *
170: * @param controller the controller suspected of failure
171: */
172: public synchronized void forceControllerDown(
173: ControllerInfo controller) {
174: if (watcher != null)
175: watcher.forceControllerDown(controller);
176: }
177:
178: /**
179: * Removes this controller from the list of alive ones. <b>Warning:</b> This
180: * function should never be called directly, only the callback should make us
181: * of this.
182: *
183: * @param controller the suspect controller
184: */
185: public synchronized void controllerDown(ControllerInfo controller) {
186: for (Iterator iter = aliveControllers.iterator(); iter
187: .hasNext();) {
188: ControllerAndVdbState ctrl = (ControllerAndVdbState) iter
189: .next();
190: if (ctrl.controller.equals(controller)) {
191: iter.remove();
192: return;
193: }
194: }
195: }
196:
197: /**
198: * Adds the specified controller to the list of alive ones at the specified
199: * position. <b>Warning:</b> This function should never be called directly,
200: * only the callback should make us of this.
201: *
202: * @param controller the controller that came back
203: * @param index the index to put the controller back at
204: */
205: public synchronized void controllerUp(ControllerInfo controller) {
206: int index = watcher.getOriginalIndexOf(controller);
207: // check bounds
208: if (aliveControllers.isEmpty())
209: // no alive controllers, add the controller at position 0
210: index = 0;
211: else if (index >= aliveControllers.size())
212: // index bigger than current list size, put the controller at the end
213: index = aliveControllers.size() - 1;
214: // add the controller and suppose that vdb is present
215: aliveControllers.add(index, new ControllerAndVdbState(
216: controller));
217: }
218:
219: /**
220: * Informs that the given controller's vdb is no more available
221: */
222: public synchronized void setVdbDownOnController(
223: ControllerInfo controller) {
224: for (Iterator iter = aliveControllers.iterator(); iter
225: .hasNext();) {
226: ControllerAndVdbState ctrl = (ControllerAndVdbState) iter
227: .next();
228: if (ctrl.controller.equals(controller)) {
229: ctrl.lastVdbFailure = System.currentTimeMillis();
230: return;
231: }
232: }
233: }
234:
235: /**
236: * Returns the number of controllers still known to be alive
237: */
238: public synchronized int numberOfAliveControllers() {
239: return aliveControllers.size();
240: }
241:
242: /**
243: * Tells if the given controller VDB is available.<br>
244: *
245: * @return false if the lastVdbFailure associated to the given controller is
246: * not older than ??? milliseconds, true otherwise
247: */
248: protected synchronized boolean isVdbUpOnController(
249: ControllerInfo controller) {
250: if (controller == null)
251: return false;
252: for (Iterator iter = aliveControllers.iterator(); iter
253: .hasNext();) {
254: ControllerAndVdbState ctrl = (ControllerAndVdbState) iter
255: .next();
256: if (ctrl.controller.equals(controller))
257: return ctrl.isVdbUp(System.currentTimeMillis());
258: }
259: return false;
260: }
261:
262: /**
263: * Returns the first controller that is alive and with a running vdb starting
264: * at rank wishedIndex in the list. wishedIndex = 0 will return the first
265: * alive controller. If the wishedIndex controller's vdb at is not available,
266: * will return the next controller in a round robin way, until finding one
267: * suitable. If no suitable controller can be found, throws a
268: * NoMoreControllerException
269: *
270: * @param wishedIndex index of the controller in the list
271: * @return a controller that is alive with its vdb up
272: * @throws NoMoreControllerException if no controller with vdb up could be
273: * found
274: */
275: protected synchronized ControllerInfo getControllerByNum(
276: int wishedIndex) throws NoMoreControllerException {
277: int nbOfControllers = aliveControllers.size();
278: if (nbOfControllers == 0)
279: throw new NoMoreControllerException();
280: // make sure given index is valid
281: if (wishedIndex >= nbOfControllers)
282: wishedIndex = 0;
283: // to stop iteration when all controllers have been tested
284: int nbTested = 0;
285: // if the vdb was not available for the whished controller, we get the next
286: // one in a round robin manner
287: ControllerAndVdbState ctrl = (ControllerAndVdbState) aliveControllers
288: .get(wishedIndex);
289: while (nbTested < nbOfControllers) {
290: if (ctrl.isVdbUp(System.currentTimeMillis())) {
291: return ctrl.controller;
292: }
293: // try next controller
294: wishedIndex++;
295: if (wishedIndex >= nbOfControllers)
296: wishedIndex = 0;
297: nbTested++;
298: ctrl = (ControllerAndVdbState) aliveControllers
299: .get(wishedIndex);
300: }
301: // all controllers tested, no more available
302: throw new NoMoreControllerException();
303: }
304: }
|