2 * Copyright 2011-2012 GRNET S.A. All rights reserved.
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the following
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
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.
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.
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.
36 package gr.grnet.aquarium
40 import com.ckkloverdos.maybe._
41 import com.ckkloverdos.props.Props
42 import com.ckkloverdos.convert.Converters.{DefaultConverters => TheDefaultConverters}
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}
50 * The master configurator. Responsible to load all of application configuration and provide the relevant services.
52 * @author Christos KK Loverdos <loverdos@gmail.com>.
54 class Configurator(val props: Props) extends Loggable {
55 import Configurator.Keys
58 * Reflectively provide a new instance of a class and configure it appropriately.
60 private[this] def newInstance[C : Manifest](className: String): C = {
61 val instanceM = Maybe(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
63 case Just(instance) ⇒ instance match {
64 case configurable: Configurable ⇒
65 Maybe(configurable configure props) match {
69 throw new AquariumException("Could not configure instance of %s".format(className), e)
71 throw new AquariumException("Could not configure instance of %s".format(className))
77 throw new AquariumException("Could not instantiate %s".format(className), e)
79 throw new AquariumException("Could not instantiate %s".format(className))
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))
91 * Initializes a store provider, according to the value configured
92 * in the configuration file. The
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))
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))
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
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))
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
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))
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))
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
140 props.get(Keys.wallet_entry_store_class) map {
142 val instance = newInstance[WalletEntryStore](className)
143 logger.info("Overriding WalletEntryStore provisioning. Implementation given by: %s".format(instance.getClass))
148 private[this] lazy val _policyStoreM: Maybe[PolicyStore] = {
149 props.get(Keys.policy_store_class) map {
151 val instance = newInstance[PolicyStore](className)
152 logger.info("Overriding PolicyStore provisioning. Implementation given by: %s".format(instance.getClass))
157 private[this] lazy val _eventsStoreFolder: Maybe[File] = {
158 props.get(Keys.events_store_folder) map {
160 val canonicalFolder = {
161 val folder = new File(folderName)
162 if(folder.isAbsolute) {
163 folder.getCanonicalFile
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
170 val canonicalPath = canonicalFolder.getCanonicalPath
172 logger.info("{} = {}", Keys.events_store_folder, canonicalPath)
174 if(canonicalFolder.exists() && !canonicalFolder.isDirectory) {
175 throw new AquariumException("%s = %s is not a folder".format(Keys.events_store_folder, canonicalFolder))
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
189 canonicalFolder.mkdirs()
195 private[this] lazy val _resEventProc = new ResourceEventProcessorService
197 private[this] lazy val _imEventProc = new IMEventProcessorService
199 private[this] lazy val _lifecycleServices = List(AkkaService, _restService, _actorProvider, _resEventProc, _imEventProc)
201 def get(key: String, default: String = ""): String = props.getOr(key, default)
203 def defaultClassLoader = Thread.currentThread().getContextClassLoader
206 * FIXME: This must be ditched.
208 * Find a file whose location can be overiden in
209 * the configuration file (e.g. policy.yaml)
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
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)
221 // Look into the configuration context
222 ResourceLocator.getResource(name) match {
223 case Just(policyResource) ⇒
224 val path = policyResource.url.getPath
231 def startServices(): Unit = {
232 _lifecycleServices.foreach(_.start())
235 def stopServices(): Unit = {
236 _lifecycleServices.reverse.foreach(_.stop())
237 // akka.actor.Actor.registry.shutdownAll()
240 def stopServicesWithDelay(millis: Long) {
245 def actorProvider = _actorProvider
247 def userStateStore = {
248 _userStateStoreM match {
250 case _ ⇒ storeProvider.userStateStore
254 def resourceEventStore = {
255 _resourceEventStoreM match {
257 case _ ⇒ storeProvider.resourceEventStore
262 _WalletEventStoreM match {
264 case _ ⇒ storeProvider.walletEntryStore
268 def userEventStore = {
269 _userEventStoreM match {
271 case _ ⇒ storeProvider.userEventStore
276 _policyStoreM match {
278 case _ ⇒ storeProvider.policyStore
282 def storeProvider = _storeProvider
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)
291 def eventsStoreFolder = _eventsStoreFolder
293 def adminCookie: MaybeOption[String] = props.get(Configurator.Keys.admin_cookie) match {
294 case just @ Just(_) ⇒ just
299 object Configurator {
300 implicit val DefaultConverters = TheDefaultConverters
302 val MasterConfName = "aquarium.properties"
304 val PolicyConfName = "policy.yaml"
306 val RolesAgreementsName = "roles-agreements.map"
308 lazy val MasterConfResource = {
309 val maybeMCResource = ResourceLocator getResource MasterConfName
310 maybeMCResource match {
311 case Just(masterConfResource) ⇒
314 throw new AquariumException("Could not find master configuration file: %s".format(MasterConfName))
316 throw new AquariumException(e, "Could not find master configuration file: %s".format(MasterConfName))
320 lazy val MasterConfProps = {
321 val maybeProps = Props apply MasterConfResource
326 throw new AquariumException("Could not load master configuration file: %s".format(MasterConfName))
328 throw new AquariumException(e, "Could not load master configuration file: %s".format(MasterConfName))
332 lazy val MasterConfigurator = {
333 Maybe(new Configurator(MasterConfProps)) match {
334 case Just(masterConf) ⇒
337 throw new AquariumException("Could not initialize master configuration file: %s".format(MasterConfName))
339 throw new AquariumException(e, "Could not initialize master configuration file: %s".format(MasterConfName))
344 * Defines the names of all the known keys inside the master properties file.
349 * The Aquarium version. Will be reported in any due occasion.
351 final val version = "version"
354 * The fully qualified name of the class that implements the `ActorProvider`.
355 * Will be instantiated reflectively and should have a public default constructor.
357 final val actor_provider_class = "actor.provider.class"
360 * The class that initializes the REST service
362 final val rest_service_class = "rest.service.class"
365 * The fully qualified name of the class that implements the `StoreProvider`.
366 * Will be instantiated reflectively and should have a public default constructor.
368 final val store_provider_class = "store.provider.class"
371 * The class that implements the User store
373 final val user_state_store_class = "user.state.store.class"
376 * The class that implements the resource event store
378 final val resource_event_store_class = "resource.event.store.class"
381 * The class that implements the IM event store
383 final val user_event_store_class = "user.event.store.class"
386 * The class that implements the wallet entries store
388 final val wallet_entry_store_class = "wallet.entry.store.class"
391 * The class that implements the wallet entries store
393 final val policy_store_class = "policy.store.class"
396 /** The lower mark for the UserActors' LRU, managed by UserActorManager.
398 * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
401 final val user_actors_lru_lower_mark = "user.actors.LRU.lower.mark"
404 * The upper mark for the UserActors' LRU, managed by UserActorManager.
406 * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
408 final val user_actors_lru_upper_mark = "user.actors.LRU.upper.mark"
411 * Comma separated list of amqp servers running in active-active
414 final val amqp_servers = "amqp.servers"
417 * Comma separated list of amqp servers running in active-active
420 final val amqp_port = "amqp.port"
423 * User name for connecting with the AMQP server
425 final val amqp_username = "amqp.username"
428 * Passwd for connecting with the AMQP server
430 final val amqp_password = "amqp.passwd"
433 * Virtual host on the AMQP server
435 final val amqp_vhost = "amqp.vhost"
438 * Comma separated list of exchanges known to aquarium
440 final val amqp_exchange = "amqp.exchange"
443 * Queues for retrieving resource events from. Multiple queues can be
444 * declared, seperated by semicolon
446 * Format is `exchange:routing.key:queue-name;<exchnage2:routing.key2:queue-name>;...`
448 final val amqp_resevents_queues = "amqp.resevents.queues"
451 * Queues for retrieving user events from. Multiple queues can be
452 * declared, seperated by semicolon
454 * Format is `exchange:routing.key:queue-name;<exchnage2:routing.key2:queue-name>;...`
456 final val amqp_userevents_queues="amqp.userevents.queues"
459 * REST service listening port.
463 final val rest_port = "rest.port"
466 * Provider for persistence services
468 final val persistence_provider = "persistence.provider"
471 * Hostname for the persistence service
473 final val persistence_host = "persistence.host"
476 * Username for connecting to the persistence service
478 final val persistence_username = "persistence.username"
481 * Password for connecting to the persistence service
483 final val persistence_password = "persistence.password"
486 * Password for connecting to the persistence service
488 final val persistence_port = "persistence.port"
491 * The DB schema to use
493 final val persistence_db = "persistence.db"
496 * Maximum number of open connections to MongoDB
498 final val mongo_connection_pool_size = "mongo.connection.pool.size"
501 * Location of the Aquarium accounting policy config file
503 final val aquarium_policy = "aquarium.policy"
506 * Location of the role-agreement mapping file
508 final val aquarium_role_agreement_map = "aquarium.role-agreement.map"
511 * A time period in milliseconds for which we can tolerate stale data regarding user state.
513 * The smaller the value, the more accurate the user credits and other state data are.
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.
519 final val user_state_timestamp_threshold = "user.state.timestamp.threshold"
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.
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
530 final val time_unit_in_millis = "time.unit.in.seconds"
533 * If a value is given to this property, then it represents a folder where all events coming to aquarium are
536 final val events_store_folder = "events.store.folder"
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]].
542 final val save_unparsed_event_im = "save.unparsed.event.im"
545 * A cookie used in every administrative REST API call, so that Aquarium knows it comes from
546 * an authorised client.
548 final val admin_cookie = "admin.cookie"