001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. 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, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.server.ldap.handlers.extended; 021 022 023 import java.util.ArrayList; 024 import java.util.Collections; 025 import java.util.HashSet; 026 import java.util.Iterator; 027 import java.util.List; 028 import java.util.Set; 029 030 import org.apache.directory.server.i18n.I18n; 031 import org.apache.directory.server.ldap.ExtendedOperationHandler; 032 import org.apache.directory.server.ldap.LdapServer; 033 import org.apache.directory.server.ldap.LdapSession; 034 import org.apache.directory.shared.ldap.message.ResultCodeEnum; 035 import org.apache.directory.shared.ldap.message.extended.GracefulDisconnect; 036 import org.apache.directory.shared.ldap.message.extended.GracefulShutdownRequest; 037 import org.apache.directory.shared.ldap.message.extended.GracefulShutdownResponse; 038 import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect; 039 import org.apache.directory.shared.ldap.message.internal.InternalExtendedRequest; 040 import org.apache.mina.core.future.WriteFuture; 041 import org.apache.mina.core.service.IoAcceptor; 042 import org.apache.mina.core.session.IoSession; 043 import org.slf4j.Logger; 044 import org.slf4j.LoggerFactory; 045 046 047 /** 048 * @org.apache.xbean.XBean 049 * 050 */ 051 public class GracefulShutdownHandler implements ExtendedOperationHandler 052 { 053 private static final Logger LOG = LoggerFactory.getLogger( GracefulShutdownHandler.class ); 054 public static final Set<String> EXTENSION_OIDS; 055 056 static 057 { 058 Set<String> set = new HashSet<String>( 3 ); 059 set.add( GracefulShutdownRequest.EXTENSION_OID ); 060 set.add( GracefulShutdownResponse.EXTENSION_OID ); 061 set.add( GracefulDisconnect.EXTENSION_OID ); 062 EXTENSION_OIDS = Collections.unmodifiableSet( set ); 063 } 064 065 066 public String getOid() 067 { 068 return GracefulShutdownRequest.EXTENSION_OID; 069 } 070 071 072 public void handleExtendedOperation( LdapSession requestor, InternalExtendedRequest req ) throws Exception 073 { 074 // make sue only the administrator can issue this shutdown request if 075 // not we respond to the requestor with with insufficientAccessRights(50) 076 if ( ! requestor.getCoreSession().isAnAdministrator() ) 077 { 078 if ( LOG.isInfoEnabled() ) 079 { 080 LOG.info( "Rejected with insufficientAccessRights to attempt for server shutdown by " 081 + requestor.getCoreSession().getEffectivePrincipal().getName() ); 082 } 083 084 requestor.getIoSession().write( new GracefulShutdownResponse( 085 req.getMessageId(), ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS ) ); 086 return; 087 } 088 089 // ------------------------------------------------------------------- 090 // handle the body of this operation below here 091 // ------------------------------------------------------------------- 092 093 IoAcceptor acceptor = ( IoAcceptor ) requestor.getIoSession().getService(); 094 List<IoSession> sessions = new ArrayList<IoSession>( 095 acceptor.getManagedSessions().values() ); 096 GracefulShutdownRequest gsreq = ( GracefulShutdownRequest ) req; 097 098 // build the graceful disconnect message with replicationContexts 099 GracefulDisconnect notice = getGracefulDisconnect( gsreq.getTimeOffline(), gsreq.getDelay() ); 100 101 // send (synch) the GracefulDisconnect to each client before unbinding 102 sendGracefulDisconnect( sessions, notice, requestor.getIoSession() ); 103 104 // wait for the specified delay before we unbind the service 105 waitForDelay( gsreq.getDelay() ); 106 107 // ------------------------------------------------------------------- 108 // unbind the server socket for the LDAP service here so no new 109 // connections are accepted while we process this shutdown request 110 // note that the following must be issued before binding the ldap 111 // service in order to prevent client disconnects on service unbind: 112 // 113 // minaRegistry.getAcceptor( service.getTransportType() ) 114 // .setDisconnectClientsOnUnbind( false ); 115 // ------------------------------------------------------------------- 116 // This might not work, either. 117 acceptor.unbind( requestor.getIoSession().getServiceAddress() ); 118 119 // ------------------------------------------------------------------- 120 // synchronously send a NoD to clients that are not aware of this resp 121 // after sending the NoD the client is disconnected if still connected 122 // ------------------------------------------------------------------- 123 sendNoticeOfDisconnect( sessions, requestor.getIoSession() ); 124 125 // ------------------------------------------------------------------- 126 // respond back to the client that requested the graceful shutdown w/ 127 // a success resultCode which confirms all clients have been notified 128 // via the graceful disconnect or NoD and the service has been unbound 129 // preventing new connections; after recieving this response the 130 // requestor should disconnect and stop using the connection 131 // ------------------------------------------------------------------- 132 sendShutdownResponse( requestor.getIoSession(), req.getMessageId() ); 133 } 134 135 136 /** 137 * Sends a successful response. 138 * 139 * @param requestor the session of the requestor 140 * @param messageId the message id associaed with this shutdown request 141 */ 142 public static void sendShutdownResponse( IoSession requestor, int messageId ) 143 { 144 GracefulShutdownResponse msg = new GracefulShutdownResponse( messageId, ResultCodeEnum.SUCCESS ); 145 WriteFuture future = requestor.write( msg ); 146 future.awaitUninterruptibly(); 147 if ( future.isWritten() ) 148 { 149 if ( LOG.isInfoEnabled() ) 150 { 151 LOG.info( "Sent GracefulShutdownResponse to client: " + requestor.getRemoteAddress() ); 152 } 153 } 154 else 155 { 156 LOG.error( I18n.err( I18n.ERR_159, requestor.getRemoteAddress() ) ); 157 } 158 requestor.close( true ); 159 } 160 161 162 /** 163 * Blocks to synchronously send the same GracefulDisconnect message to all 164 * managed sessions except for the requestor of the GracefulShutdown. 165 * 166 * @param msg the graceful disconnec extended request to send 167 * @param requestor the session of the graceful shutdown requestor 168 * @param sessions the IoSessions to send disconnect message to 169 */ 170 public static void sendGracefulDisconnect( List<IoSession> sessions, GracefulDisconnect msg, IoSession requestor ) 171 { 172 List<WriteFuture> writeFutures = new ArrayList<WriteFuture>(); 173 174 // asynchronously send GracefulDisconnection messages to all connected 175 // clients giving time for the message to arrive before we block 176 // waiting for message delivery to the client in the loop below 177 178 if ( sessions != null ) 179 { 180 for ( IoSession session : sessions ) 181 { 182 // make sure we do not send the disconnect mesasge to the 183 // client which sent the initiating GracefulShutdown request 184 if ( session.equals( requestor ) ) 185 { 186 continue; 187 } 188 189 try 190 { 191 writeFutures.add( session.write( msg ) ); 192 } 193 catch ( Exception e ) 194 { 195 LOG.warn( "Failed to write GracefulDisconnect to client session: " + session, e ); 196 } 197 } 198 } 199 200 // wait for GracefulDisconnect messages to be sent before returning 201 for ( WriteFuture future : writeFutures ) 202 { 203 try 204 { 205 future.awaitUninterruptibly( 1000 ); 206 } 207 catch ( Exception e ) 208 { 209 LOG.warn( "Failed to sent GracefulDisconnect", e ); 210 } 211 } 212 } 213 214 215 /** 216 * Blocks to synchronously send the a NoticeOfDisconnect message with 217 * the resultCode set to unavailable(52) to all managed sessions except 218 * for the requestor of the GracefulShutdown. 219 * 220 * @param requestor the session of the graceful shutdown requestor 221 * @param sessions the sessions from mina 222 */ 223 public static void sendNoticeOfDisconnect( List<IoSession> sessions, IoSession requestor ) 224 { 225 List<WriteFuture> writeFutures = new ArrayList<WriteFuture>(); 226 227 // Send Notification of Disconnection messages to all connected clients. 228 if ( sessions != null ) 229 { 230 for ( IoSession session : sessions ) 231 { 232 // make sure we do not send the disconnect mesasge to the 233 // client which sent the initiating GracefulShutdown request 234 if ( session.equals( requestor ) ) 235 { 236 continue; 237 } 238 239 try 240 { 241 writeFutures.add( session.write( NoticeOfDisconnect.UNAVAILABLE ) ); 242 } 243 catch ( Exception e ) 244 { 245 LOG.warn( "Failed to sent NoD for client: " + session, e ); 246 } 247 } 248 } 249 250 // And close the connections when the NoDs are sent. 251 Iterator<IoSession> sessionIt = sessions.iterator(); 252 253 for ( WriteFuture future : writeFutures ) 254 { 255 try 256 { 257 future.awaitUninterruptibly( 1000 ); 258 sessionIt.next().close( true ); 259 } 260 catch ( Exception e ) 261 { 262 LOG.warn( "Failed to sent NoD.", e ); 263 } 264 } 265 } 266 267 268 public static GracefulDisconnect getGracefulDisconnect( int timeOffline, int delay ) 269 { 270 // build the graceful disconnect message with replicationContexts 271 // @todo add the referral objects for replication contexts using setup code below 272 // Iterator list = nexus.listSuffixes( true ); 273 // while ( list.hasNext() ) 274 // { 275 // LdapName dn = new LdapName( ( String ) list.next() ); 276 // DirectoryPartition partition = nexus.getPartition( dn ); 277 // } 278 return new GracefulDisconnect( timeOffline, delay ); 279 } 280 281 282 public static void waitForDelay( int delay ) 283 { 284 if ( delay > 0 ) 285 { 286 // delay is in seconds 287 long delayMillis = delay * 1000L; 288 long startTime = System.currentTimeMillis(); 289 290 while ( ( System.currentTimeMillis() - startTime ) < delayMillis ) 291 { 292 try 293 { 294 Thread.sleep( 250 ); 295 } 296 catch ( InterruptedException e ) 297 { 298 LOG.warn( "Got interrupted while waiting for delay before shutdown", e ); 299 } 300 } 301 } 302 } 303 304 305 public Set<String> getExtensionOids() 306 { 307 return EXTENSION_OIDS; 308 } 309 310 311 public void setLdapServer( LdapServer ldapServer ) 312 { 313 } 314 }