Last active
December 18, 2015 20:35
-
-
Save dshulyak/c0d16988b1e26718ab66 to your computer and use it in GitHub Desktop.
basic policy engine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
Q/A | |
1. What is operation? | |
Solar client/api action | |
2. What is rule? | |
Condition, based on which we produce operations. | |
Condition should work only with local data, passing correct data will | |
be responsibility of fuel or solar | |
TODO | |
1. add topological sort for operations | |
2. reuse fuel dsl for conditions in order to integrate faster | |
""" | |
import click | |
import yaml | |
from functools import wraps | |
import networkx as nx | |
class Operation(object): | |
@property | |
def operation(self): | |
return self.__class__.__name__.upper() | |
@property | |
def identity(self): | |
return '%s_%s' % (self.operation, self.uid) | |
class Add(Operation): | |
@property | |
def inverse(self): | |
return Remove(self.uid) | |
def __init__(self, resource, uid, parameters=None): | |
self.resource = resource | |
self.uid = uid | |
self.parameters = parameters or {} | |
def __repr__(self): | |
return '{} resource={} uid={} {}'.format( | |
self.__class__.__name__.upper(), self.resource, | |
self.uid, | |
self.parameters) | |
def __eq__(self, other): | |
return all( | |
(self.__class__.__name__ == other.__class__.__name__, | |
self.resource == other.resource, | |
self.uid == other.uid)) | |
def __hash__(self): | |
return hash((self.__class__.__name__, self.resource, self.uid)) | |
class Update(Operation): | |
def __init__(self, uid, parameters): | |
self.uid = uid | |
self.parameters = parameters | |
def __repr__(self): | |
return '{} uid={} {}'.format( | |
self.__class__.__name__.upper(), self.uid, | |
self.parameters) | |
def __hash__(self): | |
return hash((self.__class__.__name, self.uid)) | |
class Remove(Add): | |
def __init__(self, uid): | |
self.uid = uid | |
def __repr__(self): | |
return '{} uid={}'.format( | |
self.__class__.__name__.upper(), | |
self.uid) | |
class Connect(Operation): | |
@property | |
def inverse(self): | |
return Disconnect(self.emitter, self.subscriber, self.parameters) | |
@property | |
def identity(self): | |
return '{}_{}_{}'.format( | |
self.operation, self.emitter, self.subscriber) | |
def __init__(self, emitter, subscriber, parameters=None): | |
self.emitter = emitter | |
self.subscriber = subscriber | |
self.parameters = parameters or {} | |
def __repr__(self): | |
return '{} from={} to={} {}'.format( | |
self.__class__.__name__.upper(), self.emitter, self.subscriber, self.parameters) | |
def __hash__(self): | |
return hash( | |
(self.__class__.__name__, self.emitter, self.subscriber)) | |
@property | |
def uid(self): | |
return '%s_%s' % (self.emitter, self.subscriber) | |
class Disconnect(Connect): | |
@property | |
def inverse(self): | |
return Connect(self.emitter, self.subscriber, self.mapping) | |
STAGED = yaml.load(""" | |
env: 2 | |
nodes: | |
- name: node1 | |
role: controller | |
uid: 1 | |
- name: node2 | |
role: compute | |
uid: 2 | |
swift: | |
enabled: true | |
ring_size: 256 | |
ceph: | |
enabled: false | |
""") | |
UPDATED = yaml.load(""" | |
env: 2 | |
nodes: | |
- name: node1 | |
role: controller | |
uid: 1 | |
- name: node2 | |
role: compute | |
uid: 2 | |
swift: | |
enabled: true | |
ring_size: 512 | |
ceph: | |
enabled: false | |
""") | |
COMMITTED = yaml.load(""" | |
env: 2 | |
nodes: | |
- name: node1 | |
role: controller | |
uid: 1 | |
- name: node1 | |
role: ceph-osd | |
uid: 1 | |
- name: node2 | |
role: compute | |
uid: 2 | |
swift: | |
enabled: false | |
ring_size: 256 | |
ceph: | |
enabled: true | |
""") | |
RULES = {} | |
def rule(conflict=()): | |
def add_rule(func): | |
global RULES | |
RULES[func.__name__] = [func, conflict] | |
@wraps(func) | |
def _wrap(*args, **kwargs): | |
return func(*args, **kwargs) | |
return _wrap | |
return add_rule | |
@rule() | |
def nodes(data): | |
for node in data.get('nodes', ()): | |
yield Add('node', | |
'node.%s' % node['uid'], | |
{'name': node['name'], | |
'env': data['env']}) | |
@rule() | |
def roles(data): | |
for node in data.get('nodes', ()): | |
uid = '%s.%s' % (node['role'], node['uid']) | |
yield Add(node['role'], uid) | |
yield Connect('node.%s' % node['uid'], uid) | |
@rule(conflict=('ceph',)) | |
def swift(data): | |
if data['swift']['enabled']: | |
controllers = filter( | |
lambda x: x['role'] == 'controller', | |
data['nodes']) | |
for controller in controllers: | |
yield Add('swift', 'swift.%s' % controller['uid'], | |
{'ring_size': data['swift']['ring_size']}) | |
yield Connect( | |
'node.%s' % controller['uid'], | |
'swift.%s' % controller['uid']) | |
@rule(conflict=('swift',)) | |
def ceph(data): | |
if data['ceph']['enabled']: | |
controllers = filter( | |
lambda x: x['role'] == 'controller', | |
data['nodes']) | |
computes = filter( | |
lambda x: x['role'] == 'computes', | |
data['nodes']) | |
for controller in controllers: | |
yield Add( | |
'ceph_controller', | |
'ceph_controller.%s' % controller['uid']) | |
yield Connect( | |
'node.%s' % controller['uid'], | |
'ceph_controller.%s' % controller['uid']) | |
for compute in computes: | |
yield Add( | |
'ceph_compute', | |
'ceph_compute.%s' % compute['uid']) | |
yield Connect( | |
'node.%s' % compute['uid'], | |
'ceph_compute.%s' % compute['uid']) | |
@click.group() | |
def main(): | |
pass | |
@main.command() | |
def staged(): | |
for rule, conflicts in RULES.values(): | |
for operation in rule(STAGED): | |
print operation | |
@main.command() | |
def committed(): | |
for rule, conflicts in RULES.values(): | |
for operation in rule(COMMITTED): | |
print operation | |
@main.command() | |
def rules(): | |
for rule in RULES: | |
print 'rule=%s conflicts=%s' % (rule, RULES[rule][1]) | |
def build(operations): | |
dg = nx.DiGraph() | |
for operation in operations: | |
dg.add_node(operation.identity, operation=operation) | |
for op in dg.nodes(): | |
operation = dg.node[op]['operation'] | |
if isinstance(operation, Disconnect): | |
dg.add_edge( | |
operation.identity, | |
'REMOVE_%s' % operation.emitter) | |
dg.add_edge( | |
operation.identity, | |
'REMOVE_%s' % operation.subscriber) | |
elif isinstance(operation, Connect): | |
dg.add_edge( | |
'ADD_%s' % operation.emitter, | |
operation.identity) | |
dg.add_edge( | |
'ADD_%s' % operation.subscriber, | |
operation.identity) | |
elif isinstance(operation, Update): | |
dg.add_edge( | |
'ADD_%s' % operation.uid, operation.identity) | |
return dg | |
@main.command() | |
@click.option('-s', default=False, is_flag=True) | |
@click.option('-c', default=False, is_flag=True) | |
def topo(s, c): | |
if s: | |
rst = map(lambda r: r[0](STAGED), RULES.values()) | |
elif c: | |
rst = map(lambda r: r[0](COMMITTED), RULES.values()) | |
dg = build([op for ops in rst for op in ops]) | |
for op in nx.topological_sort(dg): | |
print dg.node[op]['operation'] | |
@main.command() | |
@click.option('-s', default=False, is_flag=True) | |
@click.option('-c', default=False, is_flag=True) | |
@click.option('-u', default=False, is_flag=True) | |
def diff(s, c, u): | |
#todo remove this duplication | |
if c: | |
crules = map(lambda r: r[0](COMMITTED), RULES.values()) | |
cops_hash = {} | |
for rule in crules: | |
for o in rule: | |
cops_hash[o.identity] = o | |
elif u: | |
crules = map(lambda r: r[0](UPDATED), RULES.values()) | |
cops_hash = {} | |
for rule in crules: | |
for o in rule: | |
cops_hash[o.identity] = o | |
else: | |
cops_hash = {} | |
if s: | |
srules = map(lambda r: r[0](STAGED), RULES.values()) | |
sops_hash = {} | |
for rule in srules: | |
for o in rule: | |
sops_hash[o.identity] = o | |
else: | |
sops_hash = {} | |
rst = [] | |
for identity in set(cops_hash.keys() + sops_hash.keys()): | |
cop = cops_hash.get(identity) | |
sop = sops_hash.get(identity) | |
if sop and not cop: | |
rst.append(sop) | |
elif cop and not sop: | |
inv = cop.inverse | |
if isinstance(inv, Disconnect): | |
if inv.subscriber == 'controller.1': | |
import ipdb; ipdb.set_trace() | |
rst.append(cop.inverse) | |
else: | |
if cop.parameters != sop.parameters: | |
rst.append( | |
Update(sop.uid, sop.parameters)) | |
dg = build(rst) | |
for op in nx.topological_sort(dg): | |
if 'operation' in dg.node[op]: | |
operation = dg.node[op]['operation'] | |
print dg.node[op]['operation'] | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment