(file) Return to CIMListener.cpp CVS log (file) (dir) Up to [Pegasus] / pegasus / src / Pegasus / Listener

  1 karl  1.12 //%2003////////////////////////////////////////////////////////////////////////
  2 kumpf 1.1  //
  3 karl  1.12 // Copyright (c) 2000, 2001, 2002  BMC Software, Hewlett-Packard Development
  4            // Company, L. P., IBM Corp., The Open Group, Tivoli Systems.
  5            // Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L. P.;
  6            // IBM Corp.; EMC Corporation, The Open Group.
  7 kumpf 1.1  //
  8            // Permission is hereby granted, free of charge, to any person obtaining a copy
  9            // of this software and associated documentation files (the "Software"), to
 10            // deal in the Software without restriction, including without limitation the
 11            // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 12            // sell copies of the Software, and to permit persons to whom the Software is
 13            // furnished to do so, subject to the following conditions:
 14 kumpf 1.3  // 
 15 kumpf 1.1  // THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
 16            // ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
 17            // "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 18            // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 19            // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 20            // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 21            // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 22            // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 23            //
 24            //==============================================================================
 25            //
 26 tony  1.8  // Author: Dong Xiang, EMC Corporation (xiang_dong@emc.com)
 27 kumpf 1.1  //
 28 dj.gorey 1.14 // Modified By:   Dan Gorey (djgorey@us.ibm.com)
 29 a.arora  1.21 //                Amit K Arora, IBM (amita@in.ibm.com) for PEP#183
 30 kumpf    1.1  //
 31               //%/////////////////////////////////////////////////////////////////////////////
 32               
 33 tony     1.8  #include "CIMListener.h"
 34 kumpf    1.1  
 35 tony     1.8  #include <Pegasus/Common/Exception.h>
 36               #include <Pegasus/Common/SSLContext.h>
 37               #include <Pegasus/Common/Monitor.h>
 38 kumpf    1.1  #include <Pegasus/Common/HTTPAcceptor.h>
 39 kumpf    1.11 #include <Pegasus/Common/PegasusVersion.h>
 40 kumpf    1.2  
 41 kumpf    1.1  #include <Pegasus/ExportServer/CIMExportResponseEncoder.h>
 42               #include <Pegasus/ExportServer/CIMExportRequestDecoder.h>
 43               
 44 tony     1.8  #include <Pegasus/Consumer/CIMIndicationConsumer.h>
 45               #include <Pegasus/Listener/CIMListenerIndicationDispatcher.h>
 46               
 47               PEGASUS_NAMESPACE_BEGIN
 48               /////////////////////////////////////////////////////////////////////////////
 49               // CIMListenerService
 50               /////////////////////////////////////////////////////////////////////////////
 51               class CIMListenerService
 52               {
 53               public:
 54               	CIMListenerService(Uint32 portNumber, SSLContext* sslContext=NULL);
 55               	CIMListenerService(CIMListenerService& svc);
 56                 ~CIMListenerService();
 57               
 58               	void				init();
 59               	/** bind to the port
 60               	*/
 61               	void				bind();
 62               	/** runForever Main runloop for the server.
 63               	*/
 64               	void runForever();
 65 tony     1.8  	
 66               	/** Call to gracefully shutdown the server.  The server connection socket
 67               	will be closed to disable new connections from clients.
 68               	*/
 69               	void stopClientConnection();
 70               	
 71               	/** Call to gracefully shutdown the server.  It is called when the server
 72               	has been stopped and is ready to be shutdown.  Next time runForever()
 73               	is called, the server shuts down.
 74               	*/
 75               	void shutdown();
 76               	
 77               	/** Return true if the server has shutdown, false otherwise.
 78               	*/
 79               	Boolean terminated() { return _dieNow; };
 80               	
 81               	/** Call to resume the sever.
 82               	*/
 83               	void resume();
 84               	
 85               	/** Call to set the CIMServer state.  Also inform the appropriate
 86 tony     1.8  	message queues about the current state of the CIMServer.
 87               	*/
 88               	void setState(Uint32 state);
 89               	
 90               	Uint32 getOutstandingRequestCount();
 91               
 92               	/** Returns the indication listener dispatcher
 93               	 */
 94               	CIMListenerIndicationDispatcher* getIndicationDispatcher() const;
 95               
 96                 /** Returns the indication listener dispatcher
 97               	 */
 98               	void setIndicationDispatcher(CIMListenerIndicationDispatcher* dispatcher);
 99               
100               	static PEGASUS_THREAD_RETURN PEGASUS_THREAD_CDECL _listener_routine(void *param);
101               
102               private:
103               	Uint32			_portNumber;
104               	SSLContext* _sslContext;
105 dj.gorey 1.15   #ifdef PEGASUS_USE_23HTTPMONITOR_CLIENT
106 tony     1.8  	Monitor*				_monitor;
107                 HTTPAcceptor*   _acceptor;
108 dj.gorey 1.14   #else
109                	monitor_2*				_monitor;
110                 pegasus_acceptor*   _acceptor;
111                 #endif 
112               
113                 Boolean					_dieNow;
114 tony     1.8  
115                 CIMListenerIndicationDispatcher* _dispatcher;
116               
117                 CIMExportResponseEncoder* _responseEncoder;
118                 CIMExportRequestDecoder*  _requestDecoder;
119               
120               };
121               
122               CIMListenerService::CIMListenerService(Uint32 portNumber, SSLContext* sslContext)
123               :_portNumber(portNumber)
124               ,_sslContext(sslContext)
125               ,_monitor(NULL)
126               ,_acceptor(NULL)
127               ,_dieNow(false)
128               ,_dispatcher(NULL)
129               ,_responseEncoder(NULL)
130               ,_requestDecoder(NULL)
131               {
132               }
133               
134               CIMListenerService::CIMListenerService(CIMListenerService& svc)
135 tony     1.8  :_portNumber(svc._portNumber)
136               ,_sslContext(svc._sslContext)
137               ,_monitor(NULL)
138               ,_acceptor(NULL)
139               ,_dieNow(svc._dieNow)
140               ,_dispatcher(NULL)
141               ,_responseEncoder(NULL)
142               ,_requestDecoder(NULL)
143               {
144               }
145               CIMListenerService::~CIMListenerService()
146               {
147               	// if port is alive, clean up the port
148               	//if(_sslContext!=NULL)
149               	//	delete _sslContext;
150               
151               	if(_responseEncoder!=NULL)
152               		delete _responseEncoder;
153               
154               	if(_requestDecoder!=NULL)
155               		delete _requestDecoder;
156 kumpf    1.1  
157 tony     1.8  	//if(_dispatcher!=NULL)
158               	//	delete _dispatcher;
159 kumpf    1.1  
160 tony     1.8  	if(_monitor!=NULL)
161               		delete _monitor;
162 kumpf    1.1  
163 tony     1.8  	if(_acceptor!=NULL)
164               		delete _acceptor;
165               }
166 kumpf    1.1  
167 tony     1.8  void CIMListenerService::init()
168 kumpf    1.1  {
169 tony     1.8  	PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::init");
170 kumpf    1.1  
171 dj.gorey 1.15   #ifdef PEGASUS_USE_23HTTPMONITOR_CLIENT
172 tony     1.8    _monitor = new Monitor(true);
173 dj.gorey 1.14   #else
174                 _monitor = new monitor_2();
175                 #endif
176                 
177 tony     1.8  	//_dispatcher = new CIMListenerIndicationDispatcher();
178 kumpf    1.1  
179 chuck    1.20   _responseEncoder = new CIMExportResponseEncoder();
180 tony     1.8    _requestDecoder = new CIMExportRequestDecoder(
181               		_dispatcher,
182               		_responseEncoder->getQueueId());
183               
184 dj.gorey 1.15   #ifdef PEGASUS_USE_23HTTPMONITOR_CLIENT
185 dj.gorey 1.14   _acceptor = new HTTPAcceptor(
186 tony     1.8  		 _monitor, 
187               		 _requestDecoder, 
188               		 false, 
189               		 _portNumber, 
190 kumpf    1.19 		 _sslContext,
191                                false);
192 dj.gorey 1.14   #else
193                 _acceptor = new pegasus_acceptor(_monitor,
194               		   _requestDecoder,
195               		   false,
196               		   _portNumber,
197               		   _sslContext);
198                 #endif
199 kumpf    1.1  
200 chuck    1.20   bind();
201 kumpf    1.1  
202 chuck    1.20   PEG_METHOD_EXIT();
203 tony     1.8  }
204               void CIMListenerService::bind()
205               {
206 chuck    1.20   if(_acceptor!=NULL)
207                   { // Bind to the port
208                     _acceptor->bind();
209 tony     1.8  
210 chuck    1.20       PEGASUS_STD(cout) << "Listening on HTTP port " << _portNumber << PEGASUS_STD(endl);
211 tony     1.8  		
212 chuck    1.20       //listener.addAcceptor(false, portNumberHttp, false);
213                     Logger::put(Logger::STANDARD_LOG, System::CIMLISTENER, Logger::INFORMATION,
214 tony     1.8                          "Listening on HTTP port $0.", _portNumber);
215 kumpf    1.1  
216 chuck    1.20     }
217 tony     1.8  }
218 chuck    1.20 
219 tony     1.8  void CIMListenerService::runForever()
220               {
221 chuck    1.20   static int modulator = 0;
222 kumpf    1.1  
223 chuck    1.20   if(!_dieNow)
224 dj.gorey 1.14     {
225 chuck    1.20 #ifdef PEGASUS_USE_23HTTPMONITOR_CLIENT
226 a.arora  1.21       if(false == _monitor->run(500000)) 
227 chuck    1.20 	{	
228               	  modulator++;
229 a.arora  1.21       try 
230                     {
231               	     //MessageQueueService::_check_idle_flag = 1;
232               		 //MessageQueueService::_polling_sem.signal();
233               		 MessageQueueService::get_thread_pool()->kill_idle_threads();
234                     }
235               	  catch(...)
236                     {
237                     }
238 chuck    1.20 	}
239               /*
240                     if (handleShutdownSignal)
241                     {
242                       Tracer::trace(TRC_SERVER, Tracer::LEVEL3,
243               	"CIMServer::runForever - signal received.  Shutting down.");
244               
245               	ShutdownService::getInstance(this)->shutdown(true, 10, false);
246               	handleShutdownSignal = false;
247                     }
248 tony     1.8  */
249 chuck    1.20 #else
250                     _monitor->run();
251               #endif
252                   }
253 tony     1.8  }
254 kumpf    1.1  
255 tony     1.8  void CIMListenerService::shutdown()
256               {
257                   PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::shutdown()");
258 kumpf    1.1  
259 tony     1.8      _dieNow = true;
260 a.arora  1.21 #ifdef PEGASUS_USE_23HTTPMONITOR_CLIENT
261                   _monitor->tickle();
262               #endif
263 kumpf    1.1  
264 kumpf    1.4      PEG_METHOD_EXIT();
265 kumpf    1.1  }
266               
267 tony     1.8  void CIMListenerService::resume()
268 kumpf    1.1  {
269 tony     1.8      PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::resume()");
270 kumpf    1.1  
271 tony     1.8      if(_acceptor!=NULL)
272                       _acceptor->reopenConnectionSocket();
273 kumpf    1.1  
274 kumpf    1.4      PEG_METHOD_EXIT();
275 kumpf    1.1  }
276               
277 tony     1.8  void CIMListenerService::stopClientConnection()
278 kumpf    1.1  {
279 tony     1.8      PEG_METHOD_ENTER(TRC_LISTENER, "CIMListenerService::stopClientConnection()");
280 kumpf    1.1  
281 kumpf    1.10     // tell Monitor to stop listening for client connections
282 dj.gorey 1.15     #ifdef PEGASUS_USE_23HTTPMONITOR_CLIENT
283 chuck    1.22     _monitor->stopListeningForConnections(true);
284 dj.gorey 1.14     #else
285                   _monitor->stop();
286                   #endif
287 kumpf    1.10 
288                   //
289                   // Wait 150 milliseconds to allow time for the Monitor to stop
290                   // listening for client connections.
291                   //
292                   // This wait time is the timeout value for the select() call
293                   // in the Monitor's run() method (currently set to 100
294                   // milliseconds) plus a delta of 50 milliseconds.  The reason
295                   // for the wait here is to make sure that the Monitor entries
296                   // are updated before closing the connection sockets.
297                   //
298 a.arora  1.21     // pegasus_sleep(150); Not needed now due to the semaphore in the Monitor
299                   
300 tony     1.8      if(_acceptor!=NULL)
301                   _acceptor->closeConnectionSocket();
302 kumpf    1.1  
303 kumpf    1.4      PEG_METHOD_EXIT();
304 kumpf    1.1  }
305               
306 chuck    1.23 Uint32 CIMListenerService::getOutstandingRequestCount()
307               {
308                   return _acceptor->getOutstandingRequestCount();
309               }
310 tony     1.8  
311               CIMListenerIndicationDispatcher* CIMListenerService::getIndicationDispatcher() const
312 kumpf    1.1  {
313 tony     1.8  	return _dispatcher;
314               }
315               void CIMListenerService::setIndicationDispatcher(CIMListenerIndicationDispatcher* dispatcher)
316               {
317               	_dispatcher = dispatcher;
318 kumpf    1.1  }
319               
320 tony     1.8  PEGASUS_THREAD_RETURN PEGASUS_THREAD_CDECL CIMListenerService::_listener_routine(void *param)
321 kumpf    1.1  {
322 tony     1.8    CIMListenerService *svc = reinterpret_cast<CIMListenerService *>(param);
323 kumpf    1.1  
324 chuck    1.20   //svc->init(); bug 1394 
325                 while(!svc->terminated())
326                 {
327               #if defined(PEGASUS_PLATFORM_DARWIN_PPC_GNU)
328                   pthread_testcancel();
329               #endif
330                   svc->runForever();
331                 }
332 chuck    1.23 
333 chuck    1.20   delete svc;
334 tony     1.8  
335 chuck    1.20   return 0;
336 tony     1.8  }
337               static struct timeval create_time = {0, 1};
338               static struct timeval destroy_time = {15, 0};
339               static struct timeval deadlock_time = {0, 0};
340               
341               /////////////////////////////////////////////////////////////////////////////
342               // CIMListenerRep
343               /////////////////////////////////////////////////////////////////////////////
344               class CIMListenerRep
345               {
346               public:
347               	CIMListenerRep(Uint32 portNumber, SSLContext* sslContext=NULL);
348                 ~CIMListenerRep();
349               
350               	Uint32 getPortNumber() const;
351               
352               	SSLContext* getSSLContext() const;
353               	void setSSLContext(SSLContext* sslContext);
354               	
355               	void start();
356               	void stop();
357 tony     1.8  
358               	Boolean isAlive();
359               
360               	Boolean addConsumer(CIMIndicationConsumer* consumer);
361               	Boolean removeConsumer(CIMIndicationConsumer* consumer);
362               
363               private:
364 chuck    1.23   Boolean waitForPendingRequests(Uint32 shutdownTimeout);
365               
366                 Uint32 _portNumber;
367                 SSLContext* _sslContext;
368 tony     1.8  
369                 CIMListenerIndicationDispatcher* _dispatcher;
370 chuck    1.23   ThreadPool* _thread_pool;
371 chuck    1.20   CIMListenerService* _svc;  
372                 Semaphore *_listener_sem;
373 tony     1.8  };
374               
375               CIMListenerRep::CIMListenerRep(Uint32 portNumber, SSLContext* sslContext)
376               :_portNumber(portNumber)
377               ,_sslContext(sslContext)
378               ,_dispatcher(new CIMListenerIndicationDispatcher())
379               ,_thread_pool(NULL)
380 chuck    1.20 ,_svc(NULL)
381               ,_listener_sem(NULL)
382 tony     1.8  {
383               }
384               CIMListenerRep::~CIMListenerRep()
385               {
386 chuck    1.20   // if port is alive, clean up the port
387                 if (_thread_pool != NULL)
388                 {
389                   // Block incoming export requests and unbind the port
390                   _svc->stopClientConnection();
391 chuck    1.23 
392                   // Wait until pending export requests in the server are done.
393                   waitForPendingRequests(10);
394 chuck    1.20     
395                   // Shutdown the CIMListenerService
396                   _svc->shutdown();   
397                 }
398               
399                 if(_sslContext!=NULL)
400                   delete _sslContext;
401               
402                 if(_dispatcher!=NULL)
403                   delete _dispatcher;
404               
405                 if(_thread_pool!=NULL)
406                   delete _thread_pool;
407 tony     1.8  
408 chuck    1.20   if(_listener_sem!=NULL)
409                   delete _listener_sem;
410 tony     1.8  
411 chuck    1.20   // don't delete _svc, this is deleted by _listener_routine
412 tony     1.8  }
413               
414               Uint32 CIMListenerRep::getPortNumber() const
415               {
416               	return _portNumber;
417               }
418 kumpf    1.1  
419 tony     1.8  SSLContext* CIMListenerRep::getSSLContext() const
420               {
421               	return _sslContext;
422 kumpf    1.1  }
423 tony     1.8  void CIMListenerRep::setSSLContext(SSLContext* sslContext)
424               {
425               	if(_sslContext!=NULL)
426               		delete _sslContext;
427 kumpf    1.1  
428 tony     1.8  	_sslContext = sslContext;
429               }
430               void CIMListenerRep::start()
431 kumpf    1.1  {
432 chuck    1.20   // spawn a thread to do this
433                 if(_thread_pool==NULL)
434                 {
435                   CIMListenerService* svc = new CIMListenerService(_portNumber,_sslContext);
436                   try
437                   {
438                     // Try to initialize the service (bug 1394)
439                     svc->setIndicationDispatcher(_dispatcher);
440                     svc->init(); 
441                   }
442                   catch(...)
443                   {
444                     // Error. Exit without creating the ThreadPool, so that this listener
445                     // is not 'alive'
446                     delete svc;
447                     throw;
448                   }
449               
450                   _thread_pool = new ThreadPool(0, "Listener", 0, 1, 
451               				  create_time, destroy_time, deadlock_time);
452               
453 chuck    1.20     _listener_sem = new Semaphore(0);
454                   _thread_pool->allocate_and_awaken(svc,
455               				      CIMListenerService::_listener_routine,
456               				      _listener_sem);
457               
458                   _svc = svc;
459               
460                   Logger::put(Logger::STANDARD_LOG,System::CIMLISTENER,
461               		Logger::INFORMATION,
462               		"CIMListener started");
463 chuck    1.17 
464 chuck    1.20     PEGASUS_STD(cerr) << "CIMlistener started" << PEGASUS_STD(endl);
465                 }
466 tony     1.8  }
467 kumpf    1.1  
468 tony     1.8  void CIMListenerRep::stop()
469               {
470 chuck    1.20   if(_thread_pool!=NULL)
471                 { 
472                   //
473                   // Graceful shutdown of the listener service
474                   //
475               
476                   // Block incoming export requests and unbind the port
477                   _svc->stopClientConnection();
478 chuck    1.23 
479                   // Wait until pending export requests in the server are done.
480                   if (!waitForPendingRequests(10))
481                   {
482                     // Pendings requests did not finish in time.
483                     MessageLoaderParms parms("Listener.CIMListenerRep.SHUTDOWN_TIMEOUT",
484               	     "The CIMListener could not be stopped after $0 seconds because export requests have not finished.",
485               	     10);
486                     throw Exception(parms);
487                   }
488                  
489 chuck    1.20     // Shutdown the CIMListenerService
490                   _svc->shutdown();
491               
492                   // Wait for the _listener_routine thread to exit.
493                   // The thread could be delivering an export, so give it 3sec.
494                   // Note that _listener_routine deletes the CIMListenerService,
495                   // so no need to delete _svc.
496                   try
497                   {
498                     _listener_sem->time_wait(3000); 
499                   }
500                   catch (TimeOut &)
501                   {
502                     // No need to do anything, the thread pool will be deleted below
503                     // to cancel the _listener_routine thread if it is still running.
504                   }
505               
506                   delete _listener_sem;
507                   _listener_sem = NULL;
508                   
509                   // Delete the thread pool.  This cancels the listener thread if it is still
510 chuck    1.20     // running.
511                   delete _thread_pool;
512                   _thread_pool = NULL;
513               
514                   Logger::put(Logger::STANDARD_LOG,System::CIMLISTENER,
515               		Logger::INFORMATION,
516               		"CIMListener stopped");
517                 }
518 kumpf    1.1  }
519               
520 tony     1.8  Boolean CIMListenerRep::isAlive()
521 kumpf    1.1  {
522 tony     1.8  	return (_thread_pool!=NULL)?true:false;
523               }
524 kumpf    1.1  
525 tony     1.8  Boolean CIMListenerRep::addConsumer(CIMIndicationConsumer* consumer)
526               {
527               	return _dispatcher->addConsumer(consumer);
528               }
529               Boolean CIMListenerRep::removeConsumer(CIMIndicationConsumer* consumer)
530               {
531               	return _dispatcher->removeConsumer(consumer);
532               }
533 kumpf    1.1  
534 chuck    1.23 Boolean CIMListenerRep::waitForPendingRequests(Uint32 shutdownTimeout)
535               {
536                 // Wait for 10 sec max
537                 Uint32 reqCount;
538                 Uint32 countDown = shutdownTimeout * 10;
539                 for (; countDown > 0; countDown--)
540                 {
541                   reqCount = _svc->getOutstandingRequestCount();
542                   if (reqCount > 0)
543                     pegasus_sleep(100);
544                   else
545                     return true;
546                 }
547                 
548                 return false;
549               } 
550               
551 tony     1.8  /////////////////////////////////////////////////////////////////////////////
552               // CIMListener
553               /////////////////////////////////////////////////////////////////////////////
554               CIMListener::CIMListener(Uint32 portNumber, SSLContext* sslContext)
555               :_rep(new CIMListenerRep(portNumber,sslContext))
556               {
557               }
558               CIMListener::~CIMListener()
559               {
560               	if(_rep!=NULL)
561 tony     1.9  		delete static_cast<CIMListenerRep*>(_rep);
562 tony     1.8  	_rep=NULL;
563               }
564               	
565               Uint32 CIMListener::getPortNumber() const
566               {
567               	return static_cast<CIMListenerRep*>(_rep)->getPortNumber();
568 kumpf    1.1  }
569               
570 tony     1.8  SSLContext* CIMListener::getSSLContext() const
571               {
572               	return static_cast<CIMListenerRep*>(_rep)->getSSLContext();
573               }
574               void CIMListener::setSSLContext(SSLContext* sslContext)
575               {
576               	static_cast<CIMListenerRep*>(_rep)->setSSLContext(sslContext);
577               }
578               void CIMListener::start()
579 kumpf    1.1  {
580 tony     1.8  	static_cast<CIMListenerRep*>(_rep)->start();
581               }
582               void CIMListener::stop()
583               {
584               	static_cast<CIMListenerRep*>(_rep)->stop();
585               }
586 kumpf    1.1  
587 tony     1.8  Boolean CIMListener::isAlive()
588               {
589               	return static_cast<CIMListenerRep*>(_rep)->isAlive();
590               }
591 kumpf    1.1  
592 tony     1.8  Boolean CIMListener::addConsumer(CIMIndicationConsumer* consumer)
593               {
594               	return static_cast<CIMListenerRep*>(_rep)->addConsumer(consumer);
595               }
596               Boolean CIMListener::removeConsumer(CIMIndicationConsumer* consumer)
597               {
598               	return static_cast<CIMListenerRep*>(_rep)->removeConsumer(consumer);
599 kumpf    1.1  }
600               
601               PEGASUS_NAMESPACE_END

No CVS admin address has been configured
Powered by
ViewCVS 0.9.2