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.service._
45 import gr.grnet.aquarium.util.{Lifecycle, Loggable, shortNameOfClass, shortClassNameOf}
46 import gr.grnet.aquarium.store._
47 import gr.grnet.aquarium.connector.rabbitmq.service.RabbitMQService
48 import gr.grnet.aquarium.converter.StdConverters
49 import java.util.concurrent.atomic.AtomicBoolean
50 import gr.grnet.aquarium.ResourceLocator._
51 import com.ckkloverdos.sys.SysProp
54 * This is the Aquarium entry point.
56 * Responsible to load all of application configuration and provide the relevant services.
58 * @author Christos KK Loverdos <loverdos@gmail.com>.
60 final class Aquarium(val props: Props) extends Lifecycle with Loggable {
63 private[this] val _isStopping = new AtomicBoolean(false)
65 def isStopping() = _isStopping.get()
67 def debug(client: AnyRef, fmt: String, args: Any*) = {
68 logger.debug("[%s] %s".format(shortClassNameOf(client), fmt.format(args: _*)))
71 def info(client: AnyRef, fmt: String, args: Any*) = {
72 logger.info("[%s] %s".format(shortClassNameOf(client), fmt.format(args: _*)))
75 def warn(client: AnyRef, fmt: String, args: Any*) = {
76 logger.warn("[%s] %s".format(shortClassNameOf(client), fmt.format(args: _*)))
80 * Reflectively provide a new instance of a class and configure it appropriately.
82 private[this] def newInstance[C : Manifest](_className: String = ""): C = {
83 val className = _className match {
85 manifest[C].erasure.getName
91 val instanceM = MaybeEither(defaultClassLoader.loadClass(className).newInstance().asInstanceOf[C])
93 case Just(instance) ⇒ instance match {
94 case configurable: Configurable ⇒
95 val localProps = configurable.propertyPrefix match {
97 props.subsetForKeyPrefix(prefix)
103 logger.debug("Configuring {} with props", configurable.getClass.getName)
104 MaybeEither(configurable configure localProps) match {
106 logger.info("Configured {} with props", configurable.getClass.getName)
110 throw new AquariumInternalError("Could not configure instance of %s".format(className), e)
118 throw new AquariumInternalError("Could not instantiate %s".format(className), e)
123 private[this] lazy val _actorProvider = newInstance[RoleableActorProviderService](props(Keys.actor_provider_class))
126 * Initializes a store provider, according to the value configured
127 * in the configuration file. The
129 private[this] lazy val _storeProvider = newInstance[StoreProvider](props(Keys.store_provider_class))
131 private[this] lazy val _restService = newInstance[Lifecycle](props(Keys.rest_service_class))
133 private[this] lazy val _userStateStoreM: Maybe[UserStateStore] = {
134 // If there is a specific `UserStateStore` implementation specified in the
135 // properties, then this implementation overrides the user store given by
137 props.get(Keys.user_state_store_class) map { className ⇒
138 val instance = newInstance[UserStateStore](className)
139 logger.info("Overriding %s provisioning. Implementation given by: %s".format(
140 shortNameOfClass(classOf[UserStateStore]),
146 private[this] lazy val _resourceEventStoreM: Maybe[ResourceEventStore] = {
147 // If there is a specific `EventStore` implementation specified in the
148 // properties, then this implementation overrides the event store given by
150 props.get(Keys.resource_event_store_class) map { className ⇒
151 val instance = newInstance[ResourceEventStore](className)
152 logger.info("Overriding EventStore provisioning. Implementation given by: %s".format(instance.getClass))
157 private[this] lazy val _imEventStoreM: Maybe[IMEventStore] = {
158 props.get(Keys.user_event_store_class) map { className ⇒
159 val instance = newInstance[IMEventStore](className)
160 logger.info("Overriding IMEventStore provisioning. Implementation given by: %s".format(instance.getClass))
165 private[this] lazy val _WalletEventStoreM: Maybe[WalletEntryStore] = {
166 // If there is a specific `IMStore` implementation specified in the
167 // properties, then this implementation overrides the event store given by
169 props.get(Keys.wallet_entry_store_class) map {
171 val instance = newInstance[WalletEntryStore](className)
172 logger.info("Overriding WalletEntryStore provisioning. Implementation given by: %s".format(instance.getClass))
177 private[this] lazy val _policyStoreM: Maybe[PolicyStore] = {
178 props.get(Keys.policy_store_class) map {
180 val instance = newInstance[PolicyStore](className)
181 logger.info("Overriding PolicyStore provisioning. Implementation given by: %s".format(instance.getClass))
186 private[this] lazy val _eventsStoreFolder: Maybe[File] = {
187 props.get(Keys.events_store_folder) map {
189 logger.info("{} = {}", Keys.events_store_folder, folderName)
191 val canonicalFolder = {
192 val folder = new File(folderName)
193 if(folder.isAbsolute) {
194 folder.getCanonicalFile
196 logger.info("{} is not absolute, making it relative to Aquarium Home", Keys.events_store_folder)
197 new File(ResourceLocator.Homes.Folders.AquariumHome, folderName).getCanonicalFile
201 val canonicalPath = canonicalFolder.getCanonicalPath
203 if(canonicalFolder.exists() && !canonicalFolder.isDirectory) {
204 throw new AquariumInternalError("%s = %s is not a folder".format(Keys.events_store_folder, canonicalFolder))
207 // Now, events folder must be outside AQUARIUM_HOME, since AQUARIUM_HOME can be wiped out for an upgrade but
208 // we still want to keep the events.
209 val ahCanonicalPath = ResourceLocator.Homes.Folders.AquariumHome.getCanonicalPath
210 if(canonicalPath.startsWith(ahCanonicalPath)) {
211 throw new AquariumException(
212 "%s = %s is under Aquarium Home = %s".format(
213 Keys.events_store_folder,
219 canonicalFolder.mkdirs()
225 private[this] lazy val _events_store_save_rc_events = props.getBoolean(Keys.events_store_save_rc_events).getOr(false)
227 private[this] lazy val _events_store_save_im_events = props.getBoolean(Keys.events_store_save_im_events).getOr(false)
229 private[this] lazy val _converters = StdConverters.AllConverters
231 private[this] lazy val _timerService: TimerService = newInstance[SimpleTimerService]()
233 private[this] lazy val _akka = newInstance[AkkaService]()
235 private[this] lazy val _eventBus = newInstance[EventBusService]()
237 private[this] lazy val _rabbitmqService = newInstance[RabbitMQService]()
239 private[this] lazy val _storeWatcherService = newInstance[StoreWatcherService]()
241 private[this] lazy val _allServices = List(
251 def get(key: String, default: String = ""): String = props.getOr(key, default)
253 def defaultClassLoader = Thread.currentThread().getContextClassLoader
256 * FIXME: This must be ditched.
258 * Find a file whose location can be overiden in
259 * the configuration file (e.g. policy.yaml)
261 * @param name Name of the file to search for
262 * @param prop Name of the property that defines the file path
263 * @param default Name to return if no file is found
265 def findConfigFile(name: String, prop: String, default: String): File = {
266 // Check for the configured value first
267 val configured = new File(get(prop))
268 if (configured.exists)
271 // Look into the configuration context
272 ResourceLocator.getResource(name) match {
273 case Just(policyResource) ⇒
274 val path = policyResource.url.getPath
281 private[this] def startServices(): Unit = {
282 for(service ← _allServices) {
283 logStartingF(service.toString) {
289 private[this] def stopServices(): Unit = {
290 val services = _allServices.reverse
292 for(service ← services) {
293 logStoppingF(service.toString) {
294 safeUnit(service.stop())
299 def stopWithDelay(millis: Long) {
304 private[this] def configure(): Unit = {
305 logger.info("Aquarium Home = %s".format(
306 if(Homes.Folders.AquariumHome.isAbsolute)
307 Homes.Folders.AquariumHome
309 "%s [=%s]".format(Homes.Folders.AquariumHome, Homes.Folders.AquariumHome.getCanonicalPath)
312 for(folder ← this.eventsStoreFolder) {
313 logger.info("{} = {}", Aquarium.Keys.events_store_folder, folder)
315 this.eventsStoreFolder.throwMe // on error
317 for(prop ← Aquarium.PropsToShow) {
318 logger.info("{} = {}", prop.name, prop.rawValue)
322 logger.info("CONF_HERE = {}", CONF_HERE)
325 private[this] def addShutdownHooks(): Unit = {
326 Runtime.getRuntime.addShutdownHook(new Thread(new Runnable {
328 if(!_isStopping.get()) {
329 logStoppingF("Aquarium") {
338 this._isStopping.set(false)
345 this._isStopping.set(true)
349 def converters = _converters
351 def actorProvider = _actorProvider
353 def eventBus = _eventBus
355 def timerService = _timerService
357 def userStateStore = {
358 _userStateStoreM match {
360 case _ ⇒ storeProvider.userStateStore
364 def resourceEventStore = {
365 _resourceEventStoreM match {
367 case _ ⇒ storeProvider.resourceEventStore
372 _WalletEventStoreM match {
374 case _ ⇒ storeProvider.walletEntryStore
379 _imEventStoreM match {
381 case _ ⇒ storeProvider.imEventStore
386 _policyStoreM match {
388 case _ ⇒ storeProvider.policyStore
392 def storeProvider = _storeProvider
394 def withStoreProviderClass[C <: StoreProvider](spc: Class[C]): Aquarium = {
395 val map = this.props.map
396 val newMap = map.updated(Keys.store_provider_class, spc.getName)
397 val newProps = new Props(newMap)
398 new Aquarium(newProps)
401 def eventsStoreFolder = _eventsStoreFolder
403 def saveResourceEventsToEventsStoreFolder = _events_store_save_rc_events
405 def saveIMEventsToEventsStoreFolder = _events_store_save_im_events
407 def adminCookie: MaybeOption[String] = props.get(Aquarium.Keys.admin_cookie) match {
408 case just @ Just(_) ⇒ just
414 final val PropsToShow = List(
418 SysProp.JavaClassVersion,
419 SysProp.JavaLibraryPath,
420 SysProp.JavaClassPath,
421 SysProp.JavaIOTmpDir,
428 implicit val DefaultConverters = TheDefaultConverters
430 final val PolicyConfName = ResourceLocator.ResourceNames.POLICY_YAML
432 final val RolesAgreementsName = ResourceLocator.ResourceNames.ROLE_AGREEMENTS_MAP
434 final lazy val AquariumPropertiesResource = ResourceLocator.Resources.AquariumPropertiesResource
436 final lazy val AquariumProperties = {
437 val maybeProps = Props(AquariumPropertiesResource)
443 throw new AquariumInternalError(
444 "Could not load %s from %s".format(
445 ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES,
446 AquariumPropertiesResource))
450 throw new AquariumInternalError(
451 "Could not load %s from %s".format(
452 ResourceLocator.ResourceNames.AQUARIUM_PROPERTIES,
453 AquariumPropertiesResource),
459 * The main [[gr.grnet.aquarium.Aquarium]] instance.
461 final lazy val Instance = {
462 Maybe(new Aquarium(AquariumProperties)) match {
463 case Just(masterConf) ⇒
467 throw new AquariumInternalError(
468 "Could not create Aquarium configuration from %s".format(
469 AquariumPropertiesResource))
472 throw new AquariumInternalError(
473 "Could not create Aquarium configuration from %s".format(
474 AquariumPropertiesResource),
480 * Defines the names of all the known keys inside the master properties file.
485 * The Aquarium version. Will be reported in any due occasion.
487 final val version = "version"
490 * The fully qualified name of the class that implements the `RoleableActorProviderService`.
491 * Will be instantiated reflectively and should have a public default constructor.
493 final val actor_provider_class = "actor.provider.class"
496 * The class that initializes the REST service
498 final val rest_service_class = "rest.service.class"
501 * The fully qualified name of the class that implements the `StoreProvider`.
502 * Will be instantiated reflectively and should have a public default constructor.
504 final val store_provider_class = "store.provider.class"
507 * The class that implements the User store
509 final val user_state_store_class = "user.state.store.class"
512 * The class that implements the resource event store
514 final val resource_event_store_class = "resource.event.store.class"
517 * The class that implements the IM event store
519 final val user_event_store_class = "user.event.store.class"
522 * The class that implements the wallet entries store
524 final val wallet_entry_store_class = "wallet.entry.store.class"
527 * The class that implements the wallet entries store
529 final val policy_store_class = "policy.store.class"
532 /** The lower mark for the UserActors' LRU.
534 * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
537 final val user_actors_lru_lower_mark = "user.actors.LRU.lower.mark"
540 * The upper mark for the UserActors' LRU.
542 * The terminology is borrowed from the (also borrowed) Apache-lucene-solr-based implementation.
544 final val user_actors_lru_upper_mark = "user.actors.LRU.upper.mark"
547 * REST service listening port.
551 final val rest_port = "rest.port"
554 * Location of the Aquarium accounting policy config file
556 final val aquarium_policy = "aquarium.policy"
559 * Location of the role-agreement mapping file
561 final val aquarium_role_agreement_map = "aquarium.role-agreement.map"
564 * A time period in milliseconds for which we can tolerate stale data regarding user state.
566 * The smaller the value, the more accurate the user credits and other state data are.
568 * If a request for user state (e.g. balance) is received and the request timestamp exceeds
569 * the timestamp of the last known balance amount by this value, then a re-computation for
570 * the balance is triggered.
572 final val user_state_timestamp_threshold = "user.state.timestamp.threshold"
575 * The time unit is the lowest billable time period.
576 * For example, with a time unit of ten seconds, if a VM is started up and shut down in nine
577 * seconds, then the user will be billed for ten seconds.
579 * This is an overall constant. We use it as a property in order to prepare ourselves for
580 * multi-cloud setup, where the same Aquarium instance is used to bill several distinct cloud
583 final val time_unit_in_millis = "time.unit.in.seconds"
586 * If a value is given to this property, then it represents a folder where all events coming to aquarium are
589 final val events_store_folder = "events.store.folder"
592 * If this is `true` and `events.store.folder` is defined, then all resource events are
593 * also stored in `events.store.folder`.
595 * This is for debugging purposes.
597 final val events_store_save_rc_events = "events.store.save.rc.events"
600 * If this is `true` and `events.store.folder` is defined, then all IM events are
601 * also stored in `events.store.folder`.
603 * This is for debugging purposes.
605 final val events_store_save_im_events = "events.store.save.im.events"
608 * If set to `true`, then an IM event that cannot be parsed to [[gr.grnet.aquarium.event.model.im.IMEventModel]] is
609 * saved to the [[gr.grnet.aquarium.store.IMEventStore]].
611 final val save_unparsed_event_im = "save.unparsed.event.im"
614 * A cookie used in every administrative REST API call, so that Aquarium knows it comes from
615 * an authorised client.
617 final val admin_cookie = "admin.cookie"
621 final val RESTAdminHeaderName = "X-Aquarium-Admin-Cookie"
622 final val RESTAdminHeaderNameLowerCase = RESTAdminHeaderName.toLowerCase