View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.ldap.handlers.extended;
21  
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.directory.api.ldap.extras.extended.gracefulDisconnect.GracefulDisconnectResponse;
31  import org.apache.directory.api.ldap.extras.extended.gracefulDisconnect.GracefulDisconnectResponseImpl;
32  import org.apache.directory.api.ldap.extras.extended.gracefulShutdown.GracefulShutdownRequest;
33  import org.apache.directory.api.ldap.extras.extended.gracefulShutdown.GracefulShutdownResponse;
34  import org.apache.directory.api.ldap.extras.extended.gracefulShutdown.GracefulShutdownResponseImpl;
35  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
36  import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect;
37  import org.apache.directory.server.i18n.I18n;
38  import org.apache.directory.server.ldap.ExtendedOperationHandler;
39  import org.apache.directory.server.ldap.LdapServer;
40  import org.apache.directory.server.ldap.LdapSession;
41  import org.apache.mina.core.future.WriteFuture;
42  import org.apache.mina.core.service.IoAcceptor;
43  import org.apache.mina.core.session.IoSession;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  
48  /**
49   * A Handler for the GracefulShutdown extended operation
50   * 
51   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
52   */
53  public class GracefulShutdownHandler implements
54      ExtendedOperationHandler<GracefulShutdownRequest, GracefulShutdownResponse>
55  {
56      private static final Logger LOG = LoggerFactory.getLogger( GracefulShutdownHandler.class );
57      public static final Set<String> EXTENSION_OIDS;
58  
59      static
60      {
61          Set<String> set = new HashSet<>( 3 );
62          set.add( GracefulShutdownRequest.EXTENSION_OID );
63          set.add( GracefulShutdownResponse.EXTENSION_OID );
64          set.add( GracefulDisconnectResponse.EXTENSION_OID );
65          EXTENSION_OIDS = Collections.unmodifiableSet( set );
66      }
67  
68  
69      public String getOid()
70      {
71          return GracefulShutdownRequest.EXTENSION_OID;
72      }
73  
74  
75      public void handleExtendedOperation( LdapSession requestor, GracefulShutdownRequest req ) throws Exception
76      {
77          // make sue only the administrator can issue this shutdown request if 
78          // not we respond to the requestor with with insufficientAccessRights(50)
79          if ( !requestor.getCoreSession().isAnAdministrator() )
80          {
81              if ( LOG.isInfoEnabled() )
82              {
83                  LOG.info( "Rejected with insufficientAccessRights to attempt for server shutdown by "
84                      + requestor.getCoreSession().getEffectivePrincipal().getName() );
85              }
86  
87              requestor.getIoSession().write( new GracefulShutdownResponseImpl(
88                  req.getMessageId(), ResultCodeEnum.INSUFFICIENT_ACCESS_RIGHTS ) );
89              return;
90          }
91  
92          // -------------------------------------------------------------------
93          // handle the body of this operation below here
94          // -------------------------------------------------------------------
95  
96          IoAcceptor acceptor = ( IoAcceptor ) requestor.getIoSession().getService();
97          List<IoSession> sessions = new ArrayList<>(
98              acceptor.getManagedSessions().values() );
99  
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 }