Gather all services together
[aquarium] / src / main / scala / gr / grnet / aquarium / Configurator.scala
1 /*
2  * Copyright 2011-2012 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35
36 package gr.grnet.aquarium
37
38 import java.io.File
39
40 import com.ckkloverdos.maybe._
41 import com.ckkloverdos.props.Props
42 import com.ckkloverdos.convert.Converters.{DefaultConverters => TheDefaultConverters}
43
44 import gr.grnet.aquarium.service._
45 import gr.grnet.aquarium.util.{Lifecycle, Loggable}
46 import gr.grnet.aquarium.store._
47
48 /**
49  * The master configurator. Responsible to load all of application configuration and provide the relevant services.
50  *
51  * @author Christos KK Loverdos <loverdos@gmail.com>.
52  */
53 class Configurator(val props: Props) extends Loggable {
54   import Configurator.Keys
55
56   /**
57    * Reflectively provide a new instance of a class and configure it appropriately.
58    */
59   private[this] def newInstance[C : Manifest](className: String): C = {
60     val instanceM = Maybe(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
61     instanceM match {
62       case Just(instance) ⇒ instance match {
63         case configurable: Configurable ⇒
64           Maybe(configurable configure props) match {
65             case Just(_) ⇒
66               instance
67             case Failed(e) ⇒
68               throw new AquariumException("Could not configure instance of %s".format(className), e)
69             case NoVal ⇒
70               throw new AquariumException("Could not configure instance of %s".format(className))
71           }
72         case _ ⇒
73           instance
74       }
75       case Failed(e) ⇒
76         throw new AquariumException("Could not instantiate %s".format(className), e)
77       case NoVal ⇒
78         throw new AquariumException("Could not instantiate %s".format(className))
79     }
80
81   }
82
83   private[this] lazy val _actorProvider: ActorProviderService = {
84     val instance = newInstance[ActorProviderService](props.getEx(Keys.actor_provider_class))
85     logger.info("Loaded ActorProviderService: %s".format(instance.getClass))
86     instance
87   }
88
89   /**
90    * Initializes a store provider, according to the value configured
91    * in the configuration file. The
92    */
93   private[this] lazy val _storeProvider: StoreProvider = {
94     val instance = newInstance[StoreProvider](props.getEx(Keys.store_provider_class))
95     logger.info("Loaded StoreProvider: %s".format(instance.getClass))
96     instance
97   }
98   
99   private[this] lazy val _restService: Lifecycle = {
100     val instance = newInstance[Lifecycle](props.getEx(Keys.rest_service_class))
101     logger.info("Loaded RESTService: %s".format(instance.getClass))
102     instance
103   }
104
105   private[this] lazy val _userStateStoreM: Maybe[UserStateStore] = {
106     // If there is a specific `UserStateStore` implementation specified in the
107     // properties, then this implementation overrides the user store given by
108     // `StoreProvider`.
109     props.get(Keys.user_state_store_class) map { className ⇒
110       val instance = newInstance[UserStateStore](className)
111       logger.info("Overriding UserStateStore provisioning. Implementation given by: %s".format(instance.getClass))
112       instance
113     }
114   }
115
116   private[this] lazy val _resourceEventStoreM: Maybe[ResourceEventStore] = {
117     // If there is a specific `EventStore` implementation specified in the
118     // properties, then this implementation overrides the event store given by
119     // `StoreProvider`.
120     props.get(Keys.resource_event_store_class) map { className ⇒
121       val instance = newInstance[ResourceEventStore](className)
122       logger.info("Overriding EventStore provisioning. Implementation given by: %s".format(instance.getClass))
123       instance
124     }
125   }
126
127   private[this] lazy val _imEventStoreM: Maybe[IMEventStore] = {
128     props.get(Keys.user_event_store_class) map { className ⇒
129       val instance = newInstance[IMEventStore](className)
130       logger.info("Overriding IMEventStore provisioning. Implementation given by: %s".format(instance.getClass))
131       instance
132     }
133   }
134
135   private[this] lazy val _WalletEventStoreM: Maybe[WalletEntryStore] = {
136     // If there is a specific `IMStore` implementation specified in the
137     // properties, then this implementation overrides the event store given by
138     // `IMProvider`.
139     props.get(Keys.wallet_entry_store_class) map {
140       className ⇒
141         val instance = newInstance[WalletEntryStore](className)
142         logger.info("Overriding WalletEntryStore provisioning. Implementation given by: %s".format(instance.getClass))
143         instance
144     }
145   }
146
147   private[this] lazy val _policyStoreM: Maybe[PolicyStore] = {
148     props.get(Keys.policy_store_class) map {
149       className ⇒
150         val instance = newInstance[PolicyStore](className)
151         logger.info("Overriding PolicyStore provisioning. Implementation given by: %s".format(instance.getClass))
152         instance
153     }
154   }
155
156   private[this] lazy val _eventsStoreFolder: Maybe[File] = {
157     props.get(Keys.events_store_folder) map {
158       folderName ⇒
159         val canonicalFolder = {
160           val folder = new File(folderName)
161           if(folder.isAbsolute) {
162             folder.getCanonicalFile
163           } else {
164             logger.info("{} is not absolute, making it relative to AQUARIUM_HOME", Keys.events_store_folder)
165             new File(ResourceLocator.AQUARIUM_HOME_FOLDER, folderName).getCanonicalFile
166           }
167         }
168
169         val canonicalPath = canonicalFolder.getCanonicalPath
170
171         logger.info("{} = {}", Keys.events_store_folder, canonicalPath)
172
173         if(canonicalFolder.exists() && !canonicalFolder.isDirectory) {
174           throw new AquariumException("%s = %s is not a folder".format(Keys.events_store_folder, canonicalFolder))
175         }
176
177         // Now, events folder must be outside AQUARIUM_HOME, since AQUARIUM_HOME can be wiped out for an upgrade but
178         // we still want to keep the events.
179         val ahCanonicalPath = ResourceLocator.AQUARIUM_HOME_FOLDER.getCanonicalPath
180         if(canonicalPath.startsWith(ahCanonicalPath)) {
181           throw new AquariumException(
182             "%s = %s is under %s = %s".format(
183               Keys.events_store_folder, canonicalFolder,
184               ResourceLocator.AQUARIUM_HOME.name, ahCanonicalPath
185             ))
186         }
187
188         canonicalFolder.mkdirs()
189
190         canonicalFolder
191     }
192   }
193
194   private[this] lazy val _resEventProc = new ResourceEventProcessorService
195
196   private[this] lazy val _imEventProc = new IMEventProcessorService
197
198   private[this] lazy val _lifecycleServices = List(AkkaService, _restService, _actorProvider, _resEventProc, _imEventProc)
199
200   def get(key: String, default: String = ""): String = props.getOr(key, default)
201
202   def defaultClassLoader = Thread.currentThread().getContextClassLoader
203
204   /**
205    * FIXME: This must be ditched.
206    * 
207    * Find a file whose location can be overiden in
208    * the configuration file (e.g. policy.yaml)
209    *
210    * @param name Name of the file to search for
211    * @param prop Name of the property that defines the file path
212    * @param default Name to return if no file is found
213    */
214   def findConfigFile(name: String, prop: String, default: String): File = {
215     // Check for the configured value first
216     val configured = new File(get(prop))
217     if (configured.exists)
218       return configured
219
220     // Look into the configuration context
221     ResourceLocator.getResource(name) match {
222       case Just(policyResource) ⇒
223         val path = policyResource.url.getPath
224         new File(path)
225       case _ ⇒
226         new File(default)
227     }
228   }
229
230   def startServices(): Unit = {
231     _lifecycleServices.foreach(_.start())
232   }
233
234   def stopServices(): Unit = {
235     _lifecycleServices.reverse.foreach(_.stop())
236 //    akka.actor.Actor.registry.shutdownAll()
237   }
238
239   def stopServicesWithDelay(millis: Long) {
240     Thread sleep millis
241     stopServices()
242   }
243   
244   def actorProvider = _actorProvider
245
246   def userStateStore = {
247     _userStateStoreM match {
248       case Just(us) ⇒ us
249       case _        ⇒ storeProvider.userStateStore
250     }
251   }
252
253   def resourceEventStore = {
254     _resourceEventStoreM match {
255       case Just(es) ⇒ es
256       case _        ⇒ storeProvider.resourceEventStore
257     }
258   }
259
260   def walletStore = {
261     _WalletEventStoreM match {
262       case Just(es) ⇒ es
263       case _        ⇒ storeProvider.walletEntryStore
264     }
265   }
266
267   def imEventStore = {
268     _imEventStoreM match {
269       case Just(es) ⇒ es
270       case _        ⇒ storeProvider.imEventStore
271     }
272   }
273
274   def policyStore = {
275     _policyStoreM match {
276       case Just(es) ⇒ es
277       case _        ⇒ storeProvider.policyStore
278     }
279   }
280
281   def storeProvider = _storeProvider
282   
283   def withStoreProviderClass[C <: StoreProvider](spc: Class[C]): Configurator = {
284     val map = this.props.map
285     val newMap = map.updated(Keys.store_provider_class, spc.getName)
286     val newProps = new Props(newMap)
287     new Configurator(newProps)
288   }
289
290   def eventsStoreFolder = _eventsStoreFolder
291
292   def adminCookie: MaybeOption[String] = props.get(Configurator.Keys.admin_cookie) match {
293     case just @ Just(_) ⇒ just
294     case _ ⇒ NoVal
295   }
296 }
297
298 object Configurator {
299   implicit val DefaultConverters = TheDefaultConverters
300
301   val MasterConfName = "aquarium.properties"
302
303   val PolicyConfName = "policy.yaml"
304
305   val RolesAgreementsName = "roles-agreements.map"
306
307   lazy val MasterConfResource = {
308     val maybeMCResource = ResourceLocator getResource MasterConfName
309     maybeMCResource match {
310       case Just(masterConfResource) ⇒
311         masterConfResource
312       case NoVal ⇒
313         throw new AquariumException("Could not find master configuration file: %s".format(MasterConfName))
314       case Failed(e) ⇒
315         throw new AquariumException(e, "Could not find master configuration file: %s".format(MasterConfName))
316     }
317   }
318
319   lazy val MasterConfProps = {
320     val maybeProps = Props apply MasterConfResource
321     maybeProps match {
322       case Just(props) ⇒
323         props
324       case NoVal ⇒
325         throw new AquariumException("Could not load master configuration file: %s".format(MasterConfName))
326       case Failed(e) ⇒
327         throw new AquariumException(e, "Could not load master configuration file: %s".format(MasterConfName))
328     }
329   }
330
331   lazy val MasterConfigurator = {
332     Maybe(new Configurator(MasterConfProps)) match {
333       case Just(masterConf) ⇒
334         masterConf
335       case NoVal ⇒
336         throw new AquariumException("Could not initialize master configuration file: %s".format(MasterConfName))
337       case Failed(e) ⇒
338         throw new AquariumException(e, "Could not initialize master configuration file: %s".format(MasterConfName))
339     }
340   }
341
342   /**
343    * Defines the names of all the known keys inside the master properties file.
344    */
345   final object Keys {
346
347     /**
348      * The Aquarium version. Will be reported in any due occasion.
349      */
350     final val version = "version"
351
352     /**
353      * The fully qualified name of the class that implements the `ActorProviderService`.
354      * Will be instantiated reflectively and should have a public default constructor.
355      */
356     final val actor_provider_class = "actor.provider.class"
357
358     /**
359      * The class that initializes the REST service
360      */
361     final val rest_service_class = "rest.service.class"
362
363     /**
364      * The fully qualified name of the class that implements the `StoreProvider`.
365      * Will be instantiated reflectively and should have a public default constructor.
366      */
367     final val store_provider_class = "store.provider.class"
368
369     /**
370      * The class that implements the User store
371      */
372     final val user_state_store_class = "user.state.store.class"
373
374     /**
375      * The class that implements the resource event store
376      */
377     final val resource_event_store_class = "resource.event.store.class"
378
379     /**
380      * The class that implements the IM event store
381      */
382     final val user_event_store_class = "user.event.store.class"
383
384     /**
385      * The class that implements the wallet entries store
386      */
387     final val wallet_entry_store_class = "wallet.entry.store.class"
388
389     /**
390      * The class that implements the wallet entries store
391      */
392     final val policy_store_class = "policy.store.class"
393
394
395     /** The lower mark for the UserActors' LRU, managed by UserActorManager.
396      *
397      * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
398      *
399      */
400     final val user_actors_lru_lower_mark = "user.actors.LRU.lower.mark"
401
402     /**
403      * The upper mark for the UserActors' LRU, managed by UserActorManager.
404      *
405      * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
406      */
407     final val user_actors_lru_upper_mark = "user.actors.LRU.upper.mark"
408
409     /**
410      * Comma separated list of amqp servers running in active-active
411      * configuration.
412      */
413     final val amqp_servers = "amqp.servers"
414
415     /**
416      * Comma separated list of amqp servers running in active-active
417      * configuration.
418      */
419     final val amqp_port = "amqp.port"
420
421     /**
422      * User name for connecting with the AMQP server
423      */
424     final val amqp_username = "amqp.username"
425
426     /**
427      * Passwd for connecting with the AMQP server
428      */
429     final val amqp_password = "amqp.passwd"
430
431     /**
432      * Virtual host on the AMQP server
433      */
434     final val amqp_vhost = "amqp.vhost"
435
436     /**
437      * Comma separated list of exchanges known to aquarium
438      */
439     final val amqp_exchange = "amqp.exchange"
440
441     /**
442      * Queues for retrieving resource events from. Multiple queues can be
443      * declared, seperated by semicolon
444      *
445      * Format is `exchange:routing.key:queue-name;<exchnage2:routing.key2:queue-name>;...`
446      */
447     final val amqp_resevents_queues = "amqp.resevents.queues"
448
449     /**
450      * Queues for retrieving user events from. Multiple queues can be
451      * declared, seperated by semicolon
452      *
453      * Format is `exchange:routing.key:queue-name;<exchnage2:routing.key2:queue-name>;...`
454      */
455     final val amqp_userevents_queues="amqp.userevents.queues"
456
457     /**
458      * REST service listening port.
459      *
460      * Default is 8080.
461      */
462     final val rest_port = "rest.port"
463
464     /*
465      * Provider for persistence services
466      */
467     final val persistence_provider = "persistence.provider"
468
469     /**
470      * Hostname for the persistence service
471      */
472     final val persistence_host = "persistence.host"
473
474     /**
475      * Username for connecting to the persistence service
476      */
477     final val persistence_username = "persistence.username"
478
479     /**
480      *  Password for connecting to the persistence service
481      */
482     final val persistence_password = "persistence.password"
483
484     /**
485      *  Password for connecting to the persistence service
486      */
487     final val persistence_port = "persistence.port"
488
489     /**
490      *  The DB schema to use
491      */
492     final val persistence_db = "persistence.db"
493
494     /**
495      * Maximum number of open connections to MongoDB
496      */
497     final val mongo_connection_pool_size = "mongo.connection.pool.size"
498
499     /**
500      * Location of the Aquarium accounting policy config file
501      */
502     final val aquarium_policy = "aquarium.policy"
503
504     /**
505      * Location of the role-agreement mapping file
506      */
507     final val aquarium_role_agreement_map = "aquarium.role-agreement.map"
508     
509     /**
510      * A time period in milliseconds for which we can tolerate stale data regarding user state.
511      *
512      * The smaller the value, the more accurate the user credits and other state data are.
513      *
514      * If a request for user state (e.g. balance) is received and the request timestamp exceeds
515      * the timestamp of the last known balance amount by this value, then a re-computation for
516      * the balance is triggered.
517      */
518     final val user_state_timestamp_threshold = "user.state.timestamp.threshold"
519
520     /**
521      * The time unit is the lowest billable time period.
522      * For example, with a time unit of ten seconds, if a VM is started up and shut down in nine
523      * seconds, then the user will be billed for ten seconds.
524      *
525      * This is an overall constant. We use it as a property in order to prepare ourselves for
526      * multi-cloud setup, where the same Aquarium instance is used to bill several distinct cloud
527      * infrastructures.
528      */
529     final val time_unit_in_millis = "time.unit.in.seconds"
530
531     /**
532      * If a value is given to this property, then it represents a folder where all events coming to aquarium are
533      * stored.
534      */
535     final val events_store_folder = "events.store.folder"
536
537     /**
538      * If set to `true`, then an IM event that cannot be parsed to [[gr.grnet.aquarium.events.IMEvent]] is
539      * saved to the [[gr.grnet.aquarium.store.IMEventStore]].
540      */
541     final val save_unparsed_event_im = "save.unparsed.event.im"
542
543     /**
544      * A cookie used in every administrative REST API call, so that Aquarium knows it comes from
545      * an authorised client.
546      */
547     final val admin_cookie = "admin.cookie"
548   }
549 }