Merge pull request #62 from xarses/fix-resources-assignment-rebased

Fix resources assignment
This commit is contained in:
Łukasz Oleś 2015-05-29 14:06:18 +02:00
commit 394a24a821
14 changed files with 96 additions and 268 deletions

View File

@ -1,4 +1,4 @@
clients-data-file: /vagrant/clients.json
clients-data-file: /vagrant/tmp/connections.yaml
tmp: /vagrant/tmp
@ -7,9 +7,8 @@ examples-dir: /vagrant/examples
extensions-dir: /vagrant/solar/solar/extensions
file-system-db:
resources-path: ./schema/resources
storage-path: /vagrant/tmp/storage
template-dir: /vagrant/templates
resources-files-mask: /vagrant/x/resources/*/*.yaml
resource-instances-path: /vagrant/tmp/resource-instances
resources-files-mask: /vagrant/resources/*/*.yaml
node_resource_template: /vagrant/resources/ro_node/

View File

@ -6,6 +6,10 @@ import time
from solar.core import resource
from solar.core import signals
from solar.interfaces.db import get_db
db = get_db()
db.clear()
signals.Connections.clear()
@ -13,23 +17,23 @@ if os.path.exists('rs'):
shutil.rmtree('rs')
os.mkdir('rs')
node1 = resource.create('node1', 'resources/ro_node/', 'rs/', {'ip':'10.0.0.3', 'ssh_key' : '/vagrant/.vagrant/machines/solar-dev2/virtualbox/private_key', 'ssh_user':'vagrant'})
node2 = resource.create('node2', 'resources/ro_node/', 'rs/', {'ip':'10.0.0.4', 'ssh_key' : '/vagrant/.vagrant/machines/solar-dev3/virtualbox/private_key', 'ssh_user':'vagrant'})
node1 = resource.create('node1', 'resources/ro_node/', {'ip':'10.0.0.3', 'ssh_key' : '/vagrant/.vagrant/machines/solar-dev2/virtualbox/private_key', 'ssh_user':'vagrant'})
node2 = resource.create('node2', 'resources/ro_node/', {'ip':'10.0.0.4', 'ssh_key' : '/vagrant/.vagrant/machines/solar-dev3/virtualbox/private_key', 'ssh_user':'vagrant'})
mariadb_service1 = resource.create('mariadb_service1', 'resources/mariadb_service', 'rs/', {'image':'mariadb', 'root_password' : 'mariadb', 'port' : '3306', 'ip': '', 'ssh_user': '', 'ssh_key': ''})
keystone_db = resource.create('keystone_db', 'resources/mariadb_db/', 'rs/', {'db_name':'keystone_db', 'login_password':'', 'login_user':'root', 'login_port': '', 'ip':'', 'ssh_user':'', 'ssh_key':''})
keystone_db_user = resource.create('keystone_db_user', 'resources/mariadb_user/', 'rs/', {'new_user_name' : 'keystone', 'new_user_password' : 'keystone', 'db_name':'', 'login_password':'', 'login_user':'root', 'login_port': '', 'ip':'', 'ssh_user':'', 'ssh_key':''})
mariadb_service1 = resource.create('mariadb_service1', 'resources/mariadb_service', {'image':'mariadb', 'root_password' : 'mariadb', 'port' : '3306', 'ip': '', 'ssh_user': '', 'ssh_key': ''})
keystone_db = resource.create('keystone_db', 'resources/mariadb_db/', {'db_name':'keystone_db', 'login_password':'', 'login_user':'root', 'login_port': '', 'ip':'', 'ssh_user':'', 'ssh_key':''})
keystone_db_user = resource.create('keystone_db_user', 'resources/mariadb_user/', {'new_user_name' : 'keystone', 'new_user_password' : 'keystone', 'db_name':'', 'login_password':'', 'login_user':'root', 'login_port': '', 'ip':'', 'ssh_user':'', 'ssh_key':''})
keystone_config1 = resource.create('keystone_config1', 'resources/keystone_config/', 'rs/', {'config_dir' : '/etc/solar/keystone', 'ip':'', 'ssh_user':'', 'ssh_key':'', 'admin_token':'admin', 'db_password':'', 'db_name':'', 'db_user':'', 'db_host':''})
keystone_service1 = resource.create('keystone_service1', 'resources/keystone_service/', 'rs/', {'port':'5001', 'admin_port':'35357', 'image': '', 'ip':'', 'ssh_key':'', 'ssh_user':'', 'config_dir':''})
keystone_config1 = resource.create('keystone_config1', 'resources/keystone_config/', {'config_dir' : '/etc/solar/keystone', 'ip':'', 'ssh_user':'', 'ssh_key':'', 'admin_token':'admin', 'db_password':'', 'db_name':'', 'db_user':'', 'db_host':''})
keystone_service1 = resource.create('keystone_service1', 'resources/keystone_service/', {'port':'5001', 'admin_port':'35357', 'image': '', 'ip':'', 'ssh_key':'', 'ssh_user':'', 'config_dir':''})
keystone_config2 = resource.create('keystone_config2', 'resources/keystone_config/', 'rs/', {'config_dir' : '/etc/solar/keystone', 'ip':'', 'ssh_user':'', 'ssh_key':'', 'admin_token':'admin', 'db_password':'', 'db_name':'', 'db_user':'', 'db_host':''})
keystone_service2 = resource.create('keystone_service2', 'resources/keystone_service/', 'rs/', {'port':'5002', 'admin_port':'35357', 'image': '', 'ip':'', 'ssh_key':'', 'ssh_user':'', 'config_dir':''})
keystone_config2 = resource.create('keystone_config2', 'resources/keystone_config/', {'config_dir' : '/etc/solar/keystone', 'ip':'', 'ssh_user':'', 'ssh_key':'', 'admin_token':'admin', 'db_password':'', 'db_name':'', 'db_user':'', 'db_host':''})
keystone_service2 = resource.create('keystone_service2', 'resources/keystone_service/', {'port':'5002', 'admin_port':'35357', 'image': '', 'ip':'', 'ssh_key':'', 'ssh_user':'', 'config_dir':''})
haproxy_keystone_config = resource.create('haproxy_keystone1_config', 'resources/haproxy_keystone_config/', 'rs/', {'name':'keystone_config', 'listen_port':'5000', 'servers':[], 'ports':[]})
haproxy_config = resource.create('haproxy_config', 'resources/haproxy', 'rs/', {'ip':'', 'ssh_key':'', 'ssh_user':'', 'configs_names':[], 'configs_ports':[], 'listen_ports':[], 'configs':[], 'config_dir': ''})
haproxy_service = resource.create('haproxy_service', 'resources/docker_container/', 'rs/', {'image' : 'tutum/haproxy', 'ports': [], 'host_binds': [], 'volume_binds':[], 'ip':'', 'ssh_key':'', 'ssh_user':''})
haproxy_keystone_config = resource.create('haproxy_keystone1_config', 'resources/haproxy_keystone_config/', {'name':'keystone_config', 'listen_port':'5000', 'servers':[], 'ports':[]})
haproxy_config = resource.create('haproxy_config', 'resources/haproxy', {'ip':'', 'ssh_key':'', 'ssh_user':'', 'configs_names':[], 'configs_ports':[], 'listen_ports':[], 'configs':[], 'config_dir': ''})
haproxy_service = resource.create('haproxy_service', 'resources/docker_container/', {'image' : 'tutum/haproxy', 'ports': [], 'host_binds': [], 'volume_binds':[], 'ip':'', 'ssh_key':'', 'ssh_user':''})
####

View File

@ -1,9 +1,9 @@
- id: node_1
ip: 10.0.0.2
ssh_user: vagrant
ssh_private_key_path: /vagrant/tmp/keys/ssh_private
ssh_key: /vagrant/tmp/keys/ssh_private
- id: node_2
ip: 10.0.0.3
ssh_user: vagrant
ssh_private_key_path: /vagrant/tmp/keys/ssh_private
ssh_key: /vagrant/tmp/keys/ssh_private

View File

@ -1 +1 @@
clients-data-file: /tmp/clients.json
clients-data-file: /tmp/connections.yaml

View File

@ -4,3 +4,4 @@ networkx==1.9.1
PyYAML==3.11
jsonschema==2.4.0
requests==2.7.0
mock

View File

@ -4,6 +4,7 @@ set -e
VENV=x-venv
WORKSPACE=${WORKSPACE:-"/vagrant"}
CONFIG_FILE=$WORKSPACE/jenkins-config.yaml
# Setup a proper path, I call my virtualenv dir "$VENV" and

View File

@ -107,12 +107,7 @@ class Cmd(object):
lambda r: Expression(args.resources, r.get('tags', [])).evaluate(),
self._get_resources_list())
resource_instances_path = utils.read_config()['resource-instances-path']
utils.create_dir(resource_instances_path)
assign_resources_to_nodes(
resources,
nodes,
resource_instances_path)
assign_resources_to_nodes(resources, nodes)
def _get_resources_list(self):
result = []
@ -128,3 +123,7 @@ class Cmd(object):
def main():
api = Cmd()
api.parse(sys.argv[1:])
if __name__ == '__main__':
main()

View File

@ -1,19 +0,0 @@
# -*- coding: UTF-8 -*-
RESOURCE_DB = {}
def resource_add(key, value):
if key in RESOURCE_DB:
raise Exception('Key `{0}` already exists'.format(key))
RESOURCE_DB[key] = value
def get_resource(key):
return RESOURCE_DB.get(key, None)
def clear():
global RESOURCE_DB
RESOURCE_DB = {}

View File

@ -1,4 +1,4 @@
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
import os
import shutil
import tempfile
@ -25,7 +25,7 @@ class BaseHandler(object):
def _compile_action_file(self, resource, action):
action_file = resource.metadata['actions'][action]
action_file = os.path.join(resource.base_dir, 'actions', action_file)
action_file = os.path.join(resource.metadata['actions_path'], action_file)
dir_path = self.dirs[resource.name]
dest_file = tempfile.mkstemp(text=True, prefix=action, dir=dir_path)[1]
args = self._make_args(resource)
@ -43,7 +43,7 @@ class BaseHandler(object):
def _make_args(self, resource):
args = {'name': resource.name}
args['resource_dir'] = resource.base_dir
args['resource_dir'] = resource.metadata['actions_path']
args.update(resource.args)
return args

View File

@ -8,23 +8,27 @@ from copy import deepcopy
import yaml
import solar
from solar.core import actions
from solar.core import db
from solar.core import observer
from solar.core import signals
from solar import utils
from solar.core import validation
from solar.core.connections import ResourcesConnectionGraph
from solar.interfaces.db import get_db
db = get_db()
class Resource(object):
def __init__(self, name, metadata, args, base_dir, tags=None):
def __init__(self, name, metadata, args, tags=None):
self.name = name
self.base_dir = base_dir
self.metadata = metadata
self.actions = metadata['actions'].keys() if metadata['actions'] else None
self.args = {}
for arg_name, arg_value in args.items():
if not self.metadata['input'].get(arg_name):
continue
@ -42,11 +46,10 @@ class Resource(object):
def __repr__(self):
return ("Resource(name='{0}', metadata={1}, args={2}, "
"base_dir='{3}', tags={4})").format(self.name,
json.dumps(self.metadata),
json.dumps(self.args_show()),
self.base_dir,
self.tags)
"tags={3})").format(self.name,
json.dumps(self.metadata),
json.dumps(self.args_show()),
self.tags)
def args_show(self):
def formatter(v):
@ -111,20 +114,13 @@ class Resource(object):
for k, v in self.args_dict().items():
metadata['input'][k]['value'] = v
meta_file = os.path.join(self.base_dir, 'meta.yaml')
with open(meta_file, 'w') as f:
f.write(yaml.dump(metadata, default_flow_style=False))
db.add_resource(self.name, metadata)
def create(name, base_path, dest_path, args, connections={}):
def create(name, base_path, args, tags=[], connections={}):
if not os.path.exists(base_path):
raise Exception('Base resource does not exist: {0}'.format(base_path))
if not os.path.exists(dest_path):
raise Exception('Dest dir does not exist: {0}'.format(dest_path))
if not os.path.isdir(dest_path):
raise Exception('Dest path is not a directory: {0}'.format(dest_path))
dest_path = os.path.abspath(os.path.join(dest_path, name))
base_meta_file = os.path.join(base_path, 'meta.yaml')
actions_path = os.path.join(base_path, 'actions')
@ -132,42 +128,32 @@ def create(name, base_path, dest_path, args, connections={}):
meta['id'] = name
meta['version'] = '1.0.0'
meta['actions'] = {}
meta['actions_path'] = actions_path
if os.path.exists(actions_path):
for f in os.listdir(actions_path):
meta['actions'][os.path.splitext(f)[0]] = f
resource = Resource(name, meta, args, dest_path, tags=args.get('tags', []))
resource = Resource(name, meta, args, tags=tags)
signals.assign_connections(resource, connections)
# save
shutil.copytree(base_path, dest_path)
resource.save()
db.resource_add(name, resource)
return resource
def load(dest_path):
meta_file = os.path.join(dest_path, 'meta.yaml')
meta = utils.load_file(meta_file)
name = meta['id']
args = meta['input']
tags = meta.get('tags', [])
def wrap_resource(raw_resource):
name = raw_resource['id']
args = raw_resource['input']
tags = raw_resource.get('tags', [])
resource = Resource(name, meta, args, dest_path, tags=tags)
db.resource_add(name, resource)
return resource
return Resource(name, raw_resource, args, tags=tags)
def load_all(dest_path):
def load_all():
ret = {}
for name in os.listdir(dest_path):
resource_path = os.path.join(dest_path, name)
resource = load(resource_path)
for raw_resource in db.get_list('resource'):
resource = wrap_resource(raw_resource)
ret[resource.name] = resource
signals.Connections.reconnect_all()
@ -175,26 +161,28 @@ def load_all(dest_path):
return ret
def assign_resources_to_nodes(resources, nodes, dst_dir):
def assign_resources_to_nodes(resources, nodes):
for node in nodes:
for resource in resources:
merged = deepcopy(resource)
# Node specific setting should override resource's
merged.update(deepcopy(node))
merged['tags'] = list(set(node.get('tags', [])) |
set(resource.get('tags', [])))
res = deepcopy(resource)
res['tags'] = list(set(node.get('tags', [])) |
set(resource.get('tags', [])))
resource_uuid = solar.utils.generate_uuid()
# We should not generate here any uuid's, because
# a single node should be represented with a single
# resource
node_uuid = node['id']
create(
format('{0}-{1}'.format(node['id'], resource['id'])),
resource['dir_path'],
dst_dir,
merged)
node_resource_template = solar.utils.read_config()['node_resource_template']
created_resource = create(resource_uuid, resource['dir_path'], res['input'], tags=res['tags'])
created_node = create(node_uuid, node_resource_template, node, tags=node.get('tags', []))
signals.connect(created_node, created_resource)
def connect_resources(profile):
connections = profile.get('connections', [])
resources = load_all('/vagrant/tmp/resource-instances/')
graph = ResourcesConnectionGraph(connections, resources.values())
graph = ResourcesConnectionGraph(connections, load_all().values())
for connection in graph.iter_connections():
signals.connect(connection['from'], connection['to'], connection['mapping'])

View File

@ -4,9 +4,11 @@ import itertools
import networkx as nx
import os
import db
from solar import utils
from solar.interfaces.db import get_db
db = get_db()
CLIENTS_CONFIG_KEY = 'clients-data-file'
@ -46,10 +48,10 @@ class Connections(object):
:return:
"""
for emitter_name, dest_dict in CLIENTS.items():
emitter = db.get_resource(emitter_name)
emitter = db.get_obj_resource(emitter_name)
for emitter_input, destinations in dest_dict.items():
for receiver_name, receiver_input in destinations:
receiver = db.get_resource(receiver_name)
receiver = db.get_obj_resource(receiver_name)
emitter.args[emitter_input].subscribe(
receiver.args[receiver_input])
@ -107,7 +109,7 @@ def connect(emitter, receiver, mapping=None):
def disconnect(emitter, receiver):
for src, destinations in CLIENTS[emitter.name].items():
disconnect_by_src(emitter, src, receiver)
disconnect_by_src(emitter.name, src, receiver)
for destination in destinations:
receiver_input = destination[1]
@ -125,13 +127,13 @@ def disconnect_receiver_by_input(receiver, input):
"""
for emitter_name, inputs in CLIENTS.items():
emitter = db.get_resource(emitter_name)
disconnect_by_src(emitter, input, receiver)
disconnect_by_src(emitter['id'], input, receiver)
def disconnect_by_src(emitter, src, receiver):
if src in CLIENTS[emitter.name]:
CLIENTS[emitter.name][src] = [
destination for destination in CLIENTS[emitter.name][src]
def disconnect_by_src(emitter_name, src, receiver):
if src in CLIENTS[emitter_name]:
CLIENTS[emitter_name][src] = [
destination for destination in CLIENTS[emitter_name][src]
if destination[0] != receiver.name
]
@ -143,7 +145,7 @@ def notify(source, key, value):
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)
resource = db.get_obj_resource(client)
print 'Resource found', client
if resource:
resource.update({r_key: value}, emitter=source)

View File

@ -11,39 +11,26 @@ from solar import utils
from solar import errors
def get_files(path, pattern):
for root, dirs, files in os.walk(path):
for file_name in files:
if fnmatch(file_name, pattern):
yield os.path.join(root, file_name)
class FileSystemDB(DirDBM):
RESOURCES_PATH = utils.read_config()['file-system-db']['resources-path']
STORAGE_PATH = utils.read_config()['file-system-db']['storage-path']
RESOURCE_COLLECTION_NAME = 'resource'
def __init__(self):
utils.create_dir(self.STORAGE_PATH)
super(FileSystemDB, self).__init__(self.STORAGE_PATH)
self.entities = {}
def create_resource(self, resource, tags):
self.from_files(self.RESOURCES_PATH)
def get_resource(self, uid):
return self[self._make_key(self.RESOURCE_COLLECTION_NAME, uid)]
resource_uid = '{0}_{1}'.format(resource, '_'.join(tags))
data = deepcopy(self.get(resource))
data['tags'] = tags
self[resource_uid] = data
def get_obj_resource(self, uid):
from solar.core.resource import wrap_resource
raw_resource = self[self._make_key(self.RESOURCE_COLLECTION_NAME, uid)]
def get_copy(self, key):
return deepcopy(self[key])
return wrap_resource(raw_resource)
def add(self, obj):
if 'id' in obj:
self.entities[obj['id']] = obj
def store_from_file(self, file_path):
self.store(file_path)
def add_resource(self, uid, resource):
self[self._make_key(self.RESOURCE_COLLECTION_NAME, uid)] = resource
def store(self, collection, obj):
if 'id' in obj:
@ -72,20 +59,6 @@ class FileSystemDB(DirDBM):
def _make_key(self, collection, _id):
return '{0}-{1}'.format(collection, _id)
def add_resource(self, resource):
if 'id' in resource:
self.entities[resource['id']] = resource
def get(self, resource_id):
return self.entities[resource_id]
def from_files(self, path):
for file_path in get_files(path, '*.yml'):
with open(file_path) as f:
entity = f
self.add_resource(entity)
def _readFile(self, path):
return yaml.load(super(FileSystemDB, self)._readFile(path))

View File

@ -4,9 +4,11 @@ import tempfile
import unittest
import yaml
from solar.core import db
from solar.core import resource as xr
from solar.core import signals as xs
from solar.interfaces.db import get_db
db = get_db()
class BaseResourceTest(unittest.TestCase):
@ -29,8 +31,4 @@ class BaseResourceTest(unittest.TestCase):
return path
def create_resource(self, name, src, args):
dst = os.path.join(self.storage_dir, 'rs', name)
os.makedirs(dst)
return xr.create(name, src, dst, args)
return xr.create(name, src, args)

View File

@ -1,118 +0,0 @@
# x
## HAProxy deployment
```
cd /vagrant
python cli.py deploy haproxy_deployment/haproxy-deployment.yaml
```
or from Python shell:
```
from x import deployment
deployment.deploy('/vagrant/haproxy_deployment/haproxy-deployment.yaml')
```
## Usage:
Creating resources:
```
from x import resource
node1 = resource.create('node1', 'x/resources/ro_node/', 'rs/', {'ip':'10.0.0.3', 'ssh_key' : '/vagrant/tmp/keys/ssh_private', 'ssh_user':'vagrant'})
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'], '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'], '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`
To test notifications:
```
keystone_db_data.args # displays node2 IP
node2.update({'ip': '10.0.0.5'})
keystone_db_data.args # updated IP
```
If you close the Python shell you can load the resources like this:
```
from x import resource
node1 = resource.load('rs/node1')
node2 = resource.load('rs/node2')
keystone_db_data = resource.load('rs/mariadn_keystone_data')
nova_db_data = resource.load('rs/mariadb_nova_data')
```
Connections are loaded automatically.
You can also load all resources at once:
```
from x import resource
all_resources = resource.load_all('rs')
```
## CLI
You can do the above from the command-line client:
```
cd /vagrant
python cli.py resource create node1 x/resources/ro_node/ rs/ '{"ip":"10.0.0.3", "ssh_key" : "/vagrant/tmp/keys/ssh_private", "ssh_user":"vagrant"}'
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"], "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"], "ip": "", "ssh_user": "", "ssh_key": ""}'
# View resources
python cli.py resource show rs/mariadb_keystone_data
# Show all resources at location rs/
python cli.py resource show rs/ --all
# Show resources with specific tag
python cli.py resources show rs/ --tag test
# Connect resources
python cli.py connect rs/node2 rs/mariadb_keystone_data
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
# Outputs graph to 'graph.png' file, please note that arrows don't have "normal" pointers, but just the line is thicker
# please see http://networkx.lanl.gov/_modules/networkx/drawing/nx_pylab.html
python cli.py connections graph
# Disconnect
python cli.py disconnect rs/mariadb_nova_data rs/node1
# Tag a resource:
python cli.py resource tag rs/node1 test-tag
# Remove tag
python cli.py resource tag rs/node1 test-tag --delete
```