Application policies¶
https://blueprints.launchpad.net/murano/+spec/application-policies
The spec proposes mechanism that cloud operators can use to impose constraints on applications and alter application behavior without making modifications to the source code of the apps.
Problem description¶
Cloud operators often want to have control over what Murano applications can or cannot do, impose limitations on environment content or alter applications behavior somehow in a way that can be applied to a wide range of applications.
Possible policies that operator might want:
Have a black list of applications (that either has explicit application names or all apps in particular namespace) including their inheritors for particular tenant
Make each deployed environment have particular application
Want particular script to be executed on each VM spawned regardless of the image being used by the application
Want to have quota on the number of instances that can be created by a single application
Do not allow to use large instance flavors for all applications except for explicitly approved
Change application defaults so that particular promoted image will become the default value in UI for all applications that work on this OS
Send notification when number of spawned instances in particular environment exceeds quota
Change default network topology for particular tenant
Implement custom billing based on the application activities without requiring all applications to emit notifications for them.
Honor policies from the third party policy engine
As you can see all of the policies above are very different in their nature, require different inputs and different ways to apply them. Also there can be much more use cases and their variants in order have upfront support for all of them.
Thus Murano needs a generic way to write policy enforcement rules and actions.
Proposed change¶
Each policy case consists of two parts: rules specification and code that applies them.
The code is going to be written as MuranoPL classes that implement required interface. These classes are called aspects. Each aspect captures and applies one policy pattern.
Policy rules are going to be the input for those classes (their property values, similar to our regular object model).
Examples of such input might be:
Endpoint of the third party policy engine
Name of the image
List of YAQL predicates (policy rules)
Parameters of the largest flavor possible
So each instance of aspect class provided with the values for the rules parameters can be called a policy as it contains both parts of the policy definition.
Aspects are provided using the same Murano package format and reside in the
same catalog as applications are. Aspects are going to be located in the
catalog in the packages of the new type Aspect
. All aspect classes will be
inherited from the base Aspect
class which should implement some basic
interface for aspects as well as some boilerplate code.
Policy UI workflow¶
To use policies, admin should execute the following workflow in UI:
Import packages with aspect classes (usual package upload).
Create policies by adding aspects to the list of the ones he wants to apply and specifying the policy rules as an input parameters. The same UI workflow (including dynamic UI parts) that is used for adding regular Murano applications to environments is used here. The difference is that no environment is needed.
Apply aspects for particular tenants or globally. New type of GUI form must be created for that but it should be quite simple.
Instances of aspects, a.k.a policies (i.e. their serialized representation) are going to be created independently from environments and stored in a dedicated table in the database. Terms “aspect instance” and “policy” can be used interchangeable further in this spec to make accent on the one or another shade of the meaning.
In order for aspects to do their job they are going to be provided with additional capabilities that are not available to the rest of MuranoPL apps.
Aspects event hooking¶
Aspects should have ability to hook themselves to various system events produced by the engine. These could be:
Object model load events
Package load events
Object creation events that happen before/after new() function is used anywhere in the code
Garbage collection events
In the initial implementation it is proposed to introduce hooking just to object creation and package load events. It covers the most important use cases of policies (changing MuranoPL class code and changing object property values).
Two new MuranoPL classes need to be added to the Murano core library:
io.murano.policy.Aspect
- the basic interface and boilerplate code provider for all custom aspects. It has the following methods:
subscribe(notifier)
- method which invokes notifier’ssubscribe
method passing the calling aspect, event name and aspect’s method names as arguments. It subscribes aspect to notifications from notifier. Subscribes to all events in the baseAspect
class and can be overridden by inheritor not to subscribe to some events or to subscribe to some event several times with different conditions and handlers. This method is called by the engine right after aspects and notifier objects initializationcondition methods - methods which define the condition needed to be checked against some item (e.g. initializing object in case of object init event, class in case of package load event) to determine whether event handler method of the aspect should be called with that item. This group of methods include
objectInitCondition(object)
andpackageLoadCondition(class)
.modelLoadCondition(model)
can be added later. These methods returnfalse
in the base class which means no item is handled. It should be overridden by inheritor to specify actual condition. For example,objectInitCondition
can check that object is the instance of particular class or that it has some propertyhandling methods - methods which apply policy rules to some item (e.g. initializing object in case of object init event, class in case of package load event) when corresponding event occurs. So it is the core part of the policy. This group of methods include
handleObjectInit(object)
andhandlePackageLoad(class)
.handleModelLoad(model)
can be added later. The body of these methods is empty in the base class. Other handling methods can be also added and used in the inheritor class
io.murano.policy.Notifier
- the class to manage notifications of the proper aspects about system events. It has one property_handlers
which holds the dict with event names as keys and lists of event handlers as values. Each handler is a dict with aspect object, its condition method name and handling method name.Notifier
has the following methods:
subscribe(subscriber, eventName, methodName, conditionMethodName)
- method to populate notifier’s_handlers
dict. It is called by each aspect that wants to subscribe to some event
callHandler(handler, item)
- method that firstly invokes conditionMethod stated in the handler dict, in case it returnstrue
for the item, invokes handling method with the item
onEvent(event, item)
- method that invokescallHandler
for all handlers under theevent
key of_handlers
dict. This method is invoked by engine when the event occurs
Extended MuranoPL reflection¶
In addition to standard MuranoPL reflection aspects must be provided with methods that can modify MuranoPL code model:
change property declarations including default values and contracts
change methods: wrap it in another method, change argument contracts and default values
Also aspect may want to modify the metadata which defines UI layout of the
application. For example, add io.murano.metadata.forms.Hidden
class to
some property along with the inserting some predefined default value for this
property. It will result in hiding the corresponding field in GUI and using
the defined value. This part can be used once the new dynamic UI generation
from schema is introduced.
These capabilities can be written as new MuranoPL methods similar to ordinary
reflection and provided to aspects by registering them to the package context
of Aspect
packages. Thus, other MuranoPL code will be unable to make use
of these methods.
The new methods should include setDefault
, setContract
, addMeta
,
wrapMethod
.
Also the ability to set property values for other object should be provided to
aspects by setting CTX_ALLOW_PROPERTY_WRITES
flag to true
in the
Aspect
packages context.
Changes to engine workflow¶
With all that, the engine workflow with policies is the following:
User adds package to the environment
The class schema generation in engine triggers package load event
Engine obtains a list of policies that need to be applied for particular tenant (aspects and their input parameters)
Engine instantiates and initializes all those aspects and a notifier
Engine subscribes aspects to notifications from notifier
Engine loads the package and invokes
Notifier
’s methodonEvent(packageLoad)
.Notifier
finds out what aspects want to modify the package and invokes theirhandlePackageLoad()
method.Handler gets a chance to examine the code of the MuranoPL class and make necessary modifications using extended reflection capabilities.
- # During the model load, objects initializing, garbage collection engine
instantiates aspects and notifier again and runs the process of subscription => notification => modification with these events in a similar fashion.
Alternatives¶
None
Data model impact¶
Model to store policies should look roughly like this:
Table name: policy
Field |
Type |
Null |
Key |
Default |
---|---|---|---|---|
created |
datetime |
NO |
NULL |
|
updated |
datetime |
NO |
NULL |
|
id |
varchar(255) |
NO |
PRI |
NULL |
object_model |
longtext |
YES |
NULL |
|
description |
text |
YES |
NULL |
object_model field stores json-serialized representation of the policy including its rules.
description field contains optional textual information.
Model to store policies assignment to tenants:
Table name: policy_assignment
Field |
Type |
Null |
Key |
Default |
---|---|---|---|---|
created |
datetime |
NO |
NULL |
|
updated |
datetime |
NO |
NULL |
|
policy_id |
varchar(255) |
NO |
PRI |
NULL |
tenant_id |
varchar(36) |
YES |
PRI |
NULL |
NULL value of the tenant_id field means that policy should be applied to all tenants in the cloud.
REST API impact¶
Attribute |
Type |
Description |
---|---|---|
objectModel |
object |
JSON representation of policy |
tenantIds |
array |
Array of the tenants to apply policy to |
List policies
Request
Method |
URI |
Description |
---|---|---|
GET |
/policies |
List available policies |
Response
List of policies with their basic properties.
{
"policies": [
{
"created": "2014-05-14T13:02:46",
"updated": "2014-05-14T13:02:54",
"id": "2fa5ab704749444bbeafe7991b412c33",
"description": "Policy to limit possible flavor",
"tenant_ids": ["726ed856965f43cc8e565bc991fa76c3"]
},
{
"created": "2014-05-14T13:02:51",
"updated": "2014-05-14T13:02:55",
"id": "744e44812da84e858946f5d817de4f72",
"description": "Policy to restrict access to apps",
"tenant_ids": ["726ed856965f43cc8e565bc991fa76c3"]
}
]
}
Response codes:
Code |
Description |
---|---|
200 |
OK. List of policies received successfully |
403 |
User is not allowed to browse policies |
Create policy
Request
Method |
URI |
Description |
---|---|---|
POST |
/policies |
Create policy |
Body:
{
"description": "Policy to limit possible flavor",
"objectModel": {
"maxFlavor": "m1.medium",
"?": {
"type": "com.example.MyAspect",
"id": "446373ef-03b5-4925-b095-6c56568fa518"
}
}
}
Response
{
"id": "ce373a477f211e187a55404a662f968",
"description": "Policy to limit possible flavor",
"created": "2013-11-30T03:23:42Z",
"updated": "2013-11-30T03:23:44Z",
"objectModel": {
"maxFlavor": "m1.medium",
"?": {
"type": "com.example.MyAspect",
"id": "446373ef-03b5-4925-b095-6c56568fa518"
}
}
}
Response codes:
Code |
Description |
---|---|
200 |
OK. Policy has been created successfully |
400 |
Bad request. Either the format of the body is invalid or parameters doesn’t match the contracts |
403 |
User is not allowed to create policies |
404 |
Not found. Specified class doesn’t exist or it is not an Aspect |
Update policy
Request
Method |
URI |
Description |
---|---|---|
PUT |
/policies/<policy_id> |
Update policy |
Body:
{
"description": "Changed description",
"tenantIds": ["726ed856965f43cc8e565bc991fa76d8"],
"objectModel": {
"maxFlavor": "m1.large",
"?": {
"type": "com.example.MyAspect",
"id": "446373ef-03b5-4925-b095-6c56568fa518"
}
}
}
Response
{
"id": "ce373a477f211e187a55404a662f968",
"description": "Changed description",
"created": "2013-11-30T03:23:42Z",
"updated": "2013-11-30T03:39:08Z",
"tenant_ids": ["726ed856965f43cc8e565bc991fa76d8"],
"objectModel": {
"maxFlavor": "m1.large",
"?": {
"type": "com.example.MyAspect",
"id": "446373ef-03b5-4925-b095-6c56568fa518"
}
}
}
Response codes:
Code |
Description |
---|---|
200 |
OK. Policy has been updated successfully |
400 |
Bad request. Either the format of the body is invalid or parameters doesn’t match the contracts |
403 |
User is not allowed to update this policy |
404 |
Not found. Specified policy doesn’t exist |
409 |
Policy with specified name already exists |
Get policy details
Request
Method |
URI |
Description |
---|---|---|
GET |
/policies/<policy_id> |
Get policy details |
Response
{
"id": "ce373a477f211e187a55404a662f968",
"description": "Policy description",
"created": "2013-11-30T03:23:42Z",
"updated": "2013-11-30T03:39:08Z",
"tenant_ids": ["726ed856965f43cc8e565bc991fa76d8"],
"objectModel": {
"maxFlavor": "m1.medium",
"?": {
"type": "com.example.MyAspect",
"id": "446373ef-03b5-4925-b095-6c56568fa518"
}
}
}
Response codes:
Code |
Description |
---|---|
200 |
OK. Policy details have been received successfully |
403 |
User is not allowed to look up policies |
404 |
Not found. Specified policy doesn’t exist |
Delete policy
Request
Method |
URI |
Description |
---|---|---|
DELETE |
/policies/<policy_id> |
Remove policy |
Response
{
"id": "ce373a477f211e187a55404a662f968",
"description": "Policy description",
"created": "2013-11-30T03:23:42Z",
"updated": "2013-11-30T03:39:08Z",
"tenant_ids": ["726ed856965f43cc8e565bc991fa76d8"],
"objectModel": {
"maxFlavor": "m1.medium",
"?": {
"type": "com.example.MyAspect",
"id": "446373ef-03b5-4925-b095-6c56568fa518"
}
}
}
Response codes:
Code |
Description |
---|---|
200 |
OK. Policy has been removed successfully |
403 |
User is not allowed to remove this policy |
404 |
Not found. Specified policy doesn’t exist |
Versioning impact¶
None
Other end user impact¶
python-muranoclient needs to be extended with support of new REST API calls.
Deployer impact¶
Deployers will need to check the logs and UI messages informing that some parameters of deployment were changed by the policies, or for the reasons why certain applications can not be deployed etc.
Developer impact¶
Business logic of the murano applications should not be affected by the change. Policies will have the ability to modify its code on the fly during the execution.
Murano-dashboard / Horizon impact¶
The following changes need to be made in the GUI:
Form to instantiate and initialize aspect with the rules (create policy)
Form to map policies to tenants
Page to display the list of policies
Page to display each policy details
Implementation¶
Assignee(s)¶
- Primary assignee:
vakovalchuk
Work Items¶
Implement join points in engine where aspects can subscribe for events and apply changes (object init and packages load during the first phase of implementation, model load, garbage collection later).
Create MuranoPL class
Aspect
that defines the basic interface for custom aspects and place it to the core library.Create MuranoPL class
Notifier
that manages notifying process and place it to the core library.Implement extended MuranoPL reflection capabilities and make it available only to aspects.
Create database model to store policies and model to store their assignment to tenants.
Implement API for CRUD operations for the aspect instances and their assignment to tenants.
Create python-muranoclient methods to support new API.
Implement UI workflow to create policies and apply it to tenants.
Create demo packages with example policies.
Dependencies¶
Testing¶
DSL unit tests for extended MuranoPL reflection
Testrunner-based tests for the example policies functionality
Tempest tests for the new REST API calls
Unit and functional tests for the new python-muranoclient methods
Selenium tests for the new GUI workflow
Documentation Impact¶
New functionality must be properly documented
REST API specification need to be updated with new calls info