diff --git a/README.md b/README.md index 79327b87..7c115a03 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ node1 = resource.create('node1', 'x/resources/ro_node/', 'rs/', {'ip':'10.0.0.3' node2 = resource.create('node2', 'x/resources/ro_node/', 'rs/', {'ip':'10.0.0.4', 'ssh_key' : '/vagrant/tmp/keys/ssh_private', 'ssh_user':'vagrant'}) -keystone_db_data = resource.create('mariadb_keystone_data', 'x/resources/data_container/', 'rs/', {'image' : 'mariadb', 'export_volumes' : ['/var/lib/mysql'], 'host': '', 'ssh_user': '', 'ssh_key': ''}, connections={'host' : 'node2.ip', 'ssh_key':'node2.ssh_key', 'ssh_user':'node2.ssh_user'}) +keystone_db_data = resource.create('mariadb_keystone_data', 'x/resources/data_container/', 'rs/', {'image' : 'mariadb', 'export_volumes' : ['/var/lib/mysql'], 'ip': '', 'ssh_user': '', 'ssh_key': ''}, connections={'ip' : 'node2.ip', 'ssh_key':'node2.ssh_key', 'ssh_user':'node2.ssh_user'}) -nova_db_data = resource.create('mariadb_nova_data', 'x/resources/data_container/', 'rs/', {'image' : 'mariadb', 'export_volumes' : ['/var/lib/mysql'], 'host': '', 'ssh_user': '', 'ssh_key': ''}, connections={'host' : 'node1.ip', 'ssh_key':'node1.ssh_key', 'ssh_user':'node1.ssh_user'}) +nova_db_data = resource.create('mariadb_nova_data', 'x/resources/data_container/', 'rs/', {'image' : 'mariadb', 'export_volumes' : ['/var/lib/mysql'], 'ip': '', 'ssh_user': '', 'ssh_key': ''}, connections={'ip' : 'node1.ip', 'ssh_key':'node1.ssh_key', 'ssh_user':'node1.ssh_user'}) ``` to make connection after resource is created use `signal.connect` @@ -62,17 +62,21 @@ python cli.py resource create node1 x/resources/ro_node/ rs/ '{"ip":"10.0.0.3", python cli.py resource create node2 x/resources/ro_node/ rs/ '{"ip":"10.0.0.4", "ssh_key" : "/vagrant/tmp/keys/ssh_private", "ssh_user":"vagrant"}' -python cli.py resource create mariadb_keystone_data x/resources/data_container/ rs/ '{"image": "mariadb", "export_volumes" : ["/var/lib/mysql"], "host": "", "ssh_user": "", "ssh_key": ""}' +python cli.py resource create mariadb_keystone_data x/resources/data_container/ rs/ '{"image": "mariadb", "export_volumes" : ["/var/lib/mysql"], "ip": "", "ssh_user": "", "ssh_key": ""}' -python cli.py resource create mariadb_nova_data x/resources/data_container/ rs/ '{"image" : "mariadb", "export_volumes" : ["/var/lib/mysql"], "host": "", "ssh_user": "", "ssh_key": ""}' +python cli.py resource create mariadb_nova_data x/resources/data_container/ rs/ '{"image" : "mariadb", "export_volumes" : ["/var/lib/mysql"], "ip": "", "ssh_user": "", "ssh_key": ""}' # View resources python cli.py resource show rs/mariadb_keystone_data # Connect resources -python cli.py connect rs/mariadb_keystone_data rs/node2 --mapping '{"host" : "node2.ip", "ssh_key":"node2.ssh_key", "ssh_user":"node2.ssh_user"}' +python cli.py connect rs/node2 rs/mariadb_keystone_data -python cli.py connect rs/mariadb_nova_data rs/node1 --mapping '{"host" : "node1.ip", "ssh_key":"node1.ssh_key", "ssh_user":"node1.ssh_user"}' +python cli.py connect rs/node1 rs/mariadb_nova_data + +# Test update +python cli.py update rs/node2 '{"ip": "1.1.1.1"}' +python cli.py resource show rs/mariadb_keystone_data # --> IP is 1.1.1.1 # View connections python cli.py connections show diff --git a/cli.py b/cli.py index dab27e11..9e35dd9b 100644 --- a/cli.py +++ b/cli.py @@ -1,6 +1,7 @@ import click import json import networkx as nx +import os from x import resource as xr from x import signals as xs @@ -37,6 +38,22 @@ def init_cli_resource(): resource.add_command(show) + @click.command() + @click.argument('path') + @click.argument('args') + def update(args, path): + print 'Update', path, args + args = json.loads(args) + # Need to load all resources for bubbling effect to take place + # TODO: resources can be scattered around, this is a simple + # situation when we assume resources are all in one directory + base_path, name = os.path.split(path) + all = xr.load_all(base_path) + r = all[name] + r.update(args) + + resource.add_command(update) + def init_cli_connect(): @click.command() diff --git a/x/resource.py b/x/resource.py index a58d7712..2dd4db51 100644 --- a/x/resource.py +++ b/x/resource.py @@ -31,11 +31,11 @@ class Resource(object): def update(self, args): for key, value in args.iteritems(): - resource_key = self.args.get(key, None) - if resource_key: - self.args[key] = value - self.changed.append(key) - signals.notify(self, key, value) + self.args[key] = value + self.changed.append(key) + signals.notify(self, key, value) + + self.save() def action(self, action): if action in self.actions: @@ -48,6 +48,12 @@ class Resource(object): if req not in args: raise Exception('Requirement `{0}` is missing in args'.format(req)) + # TODO: versioning + def save(self): + meta_file = os.path.join(self.base_dir, 'meta.yaml') + with open(meta_file, 'w') as f: + f.write(yaml.dump(self.metadata)) + def create(name, base_path, dest_path, args, connections={}): if not os.path.exists(base_path): @@ -77,8 +83,7 @@ def create(name, base_path, dest_path, args, connections={}): #save shutil.copytree(base_path, dest_path) - with open(meta_file, 'w') as f: - f.write(yaml.dump(meta)) + resource.save() db.resource_add(name, resource) return resource diff --git a/x/signals.py b/x/signals.py index bb9f6210..59ba2e09 100644 --- a/x/signals.py +++ b/x/signals.py @@ -26,34 +26,24 @@ def guess_mapping(emitter, receiver): 'ssh_user': '.ssh_user' } - If receiver accepts inputs that are not present in emitter, - error is thrown -- such cases require manual intervention. - :param emitter: :param receiver: :return: """ + guessed = {} + for key in emitter.requires: + if key in receiver.requires: + guessed[key] = '{}.{}'.format(emitter.name, key) - ret = {} - - diff = set(receiver.requires).difference(emitter.requires) - if diff: - raise Exception( - 'The following inputs are not provided by emitter: {}.' - 'You need to set the connection manually.'.format(diff) - ) - - for key in receiver.requires: - ret[key] = '{}.{}'.format(emitter.name, key) - - return ret + return guessed def connect(emitter, receiver, mapping=None): - if mapping is None: - mapping = guess_mapping(emitter, receiver) + mapping = mapping or {} + guessed = guess_mapping(emitter, receiver) + guessed.update(mapping) - for src, dst in mapping.items(): + for src, dst in guessed.items(): CLIENTS.setdefault(emitter.name, {}) CLIENTS[emitter.name].setdefault(src, []) CLIENTS[emitter.name][src].append((receiver.name, dst)) @@ -73,26 +63,28 @@ def disconnect(emitter, receiver): def notify(source, key, value): CLIENTS.setdefault(source.name, []) + print 'Notify', source.name, key, value, CLIENTS[source.name] if key in CLIENTS[source.name]: for client, r_key in CLIENTS[source.name][key]: resource = db.get_resource(client) + print 'Resource found', client if resource: resource.update({r_key: value}) else: - #XXX resource deleted? + print 'Resource {} deleted?'.format(client) pass -def assign_connections(reciver, connections): +def assign_connections(receiver, connections): mappings = defaultdict(list) for key, dest in connections.iteritems(): resource, r_key = dest.split('.') resource = db.get_resource(resource) value = resource.args[r_key] - reciver.args[key] = value + receiver.args[key] = value mappings[resource].append((r_key, key)) for resource, r_mappings in mappings.iteritems(): - connect(resource, reciver, r_mappings) + connect(resource, receiver, r_mappings) def connection_graph():