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