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