cancel
Showing results for 
Search instead for 
Did you mean: 

Scheduled actions in multi-tenant environment

schipmolder
Champ on-the-rise
Champ on-the-rise
Hi,

I've got an bespoke AMP that includes a number of (scheduled) actions.
This all works fine, without any issues.

Now we're deploying this in a multi-tenant setup and I'm trying to get my head around how this would work.

The scheduled action beans use the CronScheduledQueryBasedTemplateActionDefinition class with property "runAsUser" set to System.
It also includes a lucene query using the "workspace://SpacesStore" store.

Now, in a multi-tenant environment, how will these queries work? e.g. will it run these for all tenants individually or once as "System" user and I need to loop over the individual tenants manually?

Ideally I get it to run the action for each tenant individually and run the query for each tenant's spaces store.

Does anyone have any pointers?

9 REPLIES 9

afaust
Legendary Innovator
Legendary Innovator
Hello,

any Quartz-jobs - an CronScheduledQueryBasedTemplateActionDefinition is one - are tenant-unaware by default and the implementation needs to handle multi-tenancy internally. In the case of CronScheduledQueryBasedTemplateActionDefinition, this is not the case, i.e. the job is not tenant-aware and will only run for one single tenant.

Which tenant that is may be determined by the runAsUser. Depending on the Alfresco version, the name of the authenticated user is in some cases implicitly being used to select the active tenant. I believe in Alfresco before 4.2, the tenant was always determined from the user name, but now there is a special TenantUtil class that holds a "tenant context". This context is only initialized when the fully authenticated user is set or some code is explicitly run with runAsTenant.

In the case of CronScheduledQueryBasedTemplateActionDefinition, the runAsUser is set as the fully authenticated user so it initializes the tenant context. So if you use "System", the action will only be run in the global tenant context. If you use "System@acme.com", the action will only be run in the "acme.com" tenant context (as local System pseudo-account).

If you want all tenants to be processed you'd have to define the same kind of bean for every tenant. Or you could write your own quartz job that handles multi-tenancy properly, i.e. iterates over all active tenants and processes each in its own transaction.

Regards
Axel

schipmolder
Champ on-the-rise
Champ on-the-rise
Thanks a lot for this incredibly useful explanation Axel!
Lots of things I didn't know and I may need to reconsider the structure of my actions, but at least I understand why i got the behaviour that i got.

schipmolder
Champ on-the-rise
Champ on-the-rise
Hi Axel,
You were spot on about the scheduled actions. I ended up looping over each active tenant and running a sub-action for each which worked just fine and i don't have to schedule it for every tenant manually.

However, I am running into one problem and that is with the OnCreateNodePolicy of the NodeServicePolicies. I have a behaviour that runs when a new file is created and this still runs fine, but how would it know which tenant's repository the file is created in? I tried using the TenantUserService's getCurrentUserDomain() but that returns an empty string and I believe the current user is the global 'admin' instead of the tenant's admin.
I also don't see how i can get the tenant's name from the item's path, so I'm a bit stuck with that one…

Thanks again!

schipmolder
Champ on-the-rise
Champ on-the-rise
A slight correction regarding the OnCreateNodePolicy, but it appears this works fine if the file is added via Share while logged in as a user within the tenant's setup, but it fails to detect the tenant when the file is created by the admin from within a scheduled action…

I was expecting the OnCreateNode to fire as the same user but it doesn't look like it does.

afaust
Legendary Innovator
Legendary Innovator
Hello,

regarding the OnCreateNodePolicy - am I safe to assume you are not using the EVERY_EVENT notification frequency? If you use TRANSACTION_COMMIT, your behaviour will be delayed until the end of the transaction. If you encapsulated your tenant-iteration within a transaction, then you have one huge transaction for all tenants and at the end of the tenant, you won't be in any tenant context exception for the global admin. If you have not done any manual transaction handling, then it is safe to assume the default Alfresco transaction behaviour for your scheduled action is the root of the issue.
In my experience, it is better to create a new transaction for each tenant nested WITHIN the runAsTenant callback, so when any behaviour is triggered, it is always guaranteed to be executed in the tenant context it was triggered in. You can use a retryingTransactionHandler for this and force it to create a new transaction in the doInTransaction call.

Regards
Axel

Hi Axel,

You're right that I'm using TRANSACTION_COMMIT frequency, but I have to admit that i'm struggling a bit getting my head around the transactions.

Am I correct to say that, a scheduled action that creates a file and a onCreateNode behaviour that is started as a result of the creation of that same file run in their own transactions and can therefor run as different users?

afaust
Legendary Innovator
Legendary Innovator
Hello,

not really - behaviours always run in the same transaction they were triggered in. But within that single transaction, the context may change. The typical context nesting with normal user interactions (i.e. via web scripts) is tenant/authentication->transaction->logic but when you handle tenants in custom code, it is easy to end up with transaction->tenant/authentication->logic. TRANSACTION_COMMIT is always executed as a transaction listener right before the commit, so you may not be executing in any tenant/authentication context that you've used during the transaction and which were active when a behaviour was triggered.

Regards
Axel

schipmolder
Champ on-the-rise
Champ on-the-rise
Thanks Axel, I must admit that the whole transaction system confuses me, but I roughly understand why it doesn't work.
I also managed to work around it by changing the setup from

"action creating node" -> "policy responds to node creation"

to

"action creating node and calls other action to process that node" -> "second action processes node correctly"

I don't really know why this does work but I can live with that.

Thanks a lot for your help, much appreciated.

afaust
Legendary Innovator
Legendary Innovator
Hello,

your solution works because of two different reasons (depending on how you trigger the action):
a) any action called synchronously will inherit the correct authentication context
b) any action triggered for asynchronous execution will actually "remember" the authentication context when triggered and use that when it is being executed

The variant a) is the same as if you had reworked your policy to use EVERY_EVENT as the notification frequency which executes the policy immediately when the creation occurs.

Regards
Axel