From fce7070e876a58b91ad06442bb187f7c7f732cf2 Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Mon, 1 Jun 2015 17:05:17 +0200 Subject: [PATCH 1/4] Add error passing from ansible and handling it in commit procedure --- solar/solar/core/actions.py | 2 +- solar/solar/core/handlers/ansible.py | 2 ++ solar/solar/operations.py | 40 +++++++++++++++++++--------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/solar/solar/core/actions.py b/solar/solar/core/actions.py index 5c694a38..eb209f11 100644 --- a/solar/solar/core/actions.py +++ b/solar/solar/core/actions.py @@ -5,7 +5,7 @@ import handlers def resource_action(resource, action): handler = resource.metadata['handler'] with handlers.get(handler)([resource]) as h: - h.action(resource, action) + return h.action(resource, action) def tag_action(tag, action): diff --git a/solar/solar/core/handlers/ansible.py b/solar/solar/core/handlers/ansible.py index 4ae97232..7ce7f456 100644 --- a/solar/solar/core/handlers/ansible.py +++ b/solar/solar/core/handlers/ansible.py @@ -3,6 +3,7 @@ import os import subprocess from solar.core.handlers.base import BaseHandler +from solar.state import STATES class Ansible(BaseHandler): @@ -13,6 +14,7 @@ class Ansible(BaseHandler): print 'playbook_file', playbook_file call_args = ['ansible-playbook', '--module-path', '/vagrant/library', '-i', inventory_file, playbook_file] print 'EXECUTING: ', ' '.join(call_args) + try: subprocess.check_output(call_args) except subprocess.CalledProcessError as e: diff --git a/solar/solar/operations.py b/solar/solar/operations.py index e1c43842..e065931d 100644 --- a/solar/solar/operations.py +++ b/solar/solar/operations.py @@ -11,6 +11,7 @@ db = get_db() from dictdiffer import diff, patch, revert import networkx as nx +import subprocess def guess_action(from_, to): @@ -78,6 +79,13 @@ def stage_changes(): return log +def execute(res, action): + try: + actions.resource_action(res, action) + return state.STATES.success + except subprocess.CalledProcessError: + return state.STATES.error + def commit_changes(): # just shortcut to test stuff commited = state.CD() @@ -86,25 +94,33 @@ def commit_changes(): resources = resource.load_all() while staged: - l = staged.popleft() - wrapper = resources[l.res] + li = staged.popleft() + wrapper = resources[li.res] - staged_data = patch(l.diff, commited.get(l.res, {})) + staged_data = patch(li.diff, commited.get(li.res, {})) # TODO(dshulyak) think about this hack for update - if l.action == 'update': - commited_args = commited[l.res]['args'] + if li.action == 'update': + commited_args = commited[li.res]['args'] wrapper.update(commited_args) - actions.resource_action(wrapper, 'remove') + result_state = execute(wrapper, 'remove') - wrapper.update(staged_data.get('args', {})) - actions.resource_action(wrapper, 'run') + if result_state is state.STATES.success: + wrapper.update(staged_data.get('args', {})) + result_state = execute(wrapper, 'run') else: - actions.resource_action(wrapper, l.action) + result_state = execute(wrapper, li.action) - commited[l.res] = staged_data - l.state = state.STATES.success - history.add(l) + # resource_action return None in case there is no actions + result_state = result_state or state.STATES.success + + commited[li.res] = staged_data + li.state = result_state + + history.add(li) + + if result_state is state.STATES.error: + return def rollback(log_item): From 6ea8544ed92c0ee126c9dfde8cb6f9b52045dbe2 Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Tue, 2 Jun 2015 15:00:49 +0200 Subject: [PATCH 2/4] Fixing rollback and other operations --- cli.py | 24 ++++---- resources/keystone_service/actions/run.yml | 2 +- solar/solar/core/observer.py | 3 +- solar/solar/core/signals.py | 7 ++- solar/solar/operations.py | 70 +++++++++++++--------- 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/cli.py b/cli.py index 37e3e117..effc1019 100755 --- a/cli.py +++ b/cli.py @@ -92,19 +92,13 @@ def init_cli_resource(): resource.add_command(show) @click.command() - @click.argument('path') + @click.argument('name') @click.argument('args') - def update(args, path): - print 'Update', path, args + def update(name, 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) + all = xr.load_all() r = all[name] r.update(args) - resource.add_command(update) @@ -154,8 +148,12 @@ def init_changes(): changes.add_command(stage) @click.command() - def commit(): - operations.commit_changes() + @click.option('--one', is_flag=True, default=False) + def commit(one): + if one: + operations.commit_one() + else: + operations.commit_changes() changes.add_command(commit) @@ -180,6 +178,10 @@ def init_changes(): changes.add_command(rollback) + @click.command() + def replay(): + operations.replay_errors() + changes.add_command(replay) def init_cli_connections(): @click.group() diff --git a/resources/keystone_service/actions/run.yml b/resources/keystone_service/actions/run.yml index 9aca8f54..93d65f03 100644 --- a/resources/keystone_service/actions/run.yml +++ b/resources/keystone_service/actions/run.yml @@ -15,6 +15,6 @@ - {{ admin_port }}:35357 volumes: - {{ config_dir }}:/etc/keystone - - name: wait for keystone wait_for: host={{ip}} port={{port}} timeout=20 + diff --git a/solar/solar/core/observer.py b/solar/solar/core/observer.py index 3c3cd718..27108834 100644 --- a/solar/solar/core/observer.py +++ b/solar/solar/core/observer.py @@ -17,7 +17,8 @@ class BaseObserver(object): self.receivers = [] def log(self, msg): - print '{} {}'.format(self, msg) + pass + #print '{} {}'.format(self, msg) def __repr__(self): return '[{}:{}] {}'.format(self.attached_to.name, self.name, self.value) diff --git a/solar/solar/core/signals.py b/solar/solar/core/signals.py index a51486e6..a819422e 100644 --- a/solar/solar/core/signals.py +++ b/solar/solar/core/signals.py @@ -121,9 +121,10 @@ def disconnect(emitter, receiver): for destination in destinations: receiver_input = destination[1] - if receiver.args[receiver_input].type_ != 'list': - print 'Removing input {} from {}'.format(receiver_input, receiver.name) - emitter.args[src].unsubscribe(receiver.args[receiver_input]) + if receiver_input in receiver.args: + if receiver.args[receiver_input].type_ != 'list': + print 'Removing input {} from {}'.format(receiver_input, receiver.name) + emitter.args[src].unsubscribe(receiver.args[receiver_input]) def disconnect_receiver_by_input(receiver, input): diff --git a/solar/solar/operations.py b/solar/solar/operations.py index e065931d..2b4591e5 100644 --- a/solar/solar/operations.py +++ b/solar/solar/operations.py @@ -86,6 +86,46 @@ def execute(res, action): except subprocess.CalledProcessError: return state.STATES.error + +def commit(li, resources): + commited = state.CD() + history = state.CL() + staged = state.SL() + + wrapper = resources[li.res] + + staged_data = patch(li.diff, commited.get(li.res, {})) + + # TODO(dshulyak) think about this hack for update + if li.action == 'update': + commited_args = commited[li.res]['args'] + wrapper.update(commited_args) + result_state = actions.resource_action(wrapper, 'remove') + + if result_state is state.STATES.success: + wrapper.update(staged_data.get('args', {})) + result_state = actions.resource_action(wrapper, 'run') + else: + result_state = actions.resource_action(wrapper, li.action) + + # resource_action return None in case there is no actions + result_state = result_state or state.STATES.success + + commited[li.res] = staged_data + li.state = result_state + + history.add(li) + + if result_state is state.STATES.error: + raise Exception('Failed') + + +def commit_one(): + staged = state.SL() + resources = resource.load_all() + commit(staged.popleft(), resources) + + def commit_changes(): # just shortcut to test stuff commited = state.CD() @@ -94,33 +134,7 @@ def commit_changes(): resources = resource.load_all() while staged: - li = staged.popleft() - wrapper = resources[li.res] - - staged_data = patch(li.diff, commited.get(li.res, {})) - - # TODO(dshulyak) think about this hack for update - if li.action == 'update': - commited_args = commited[li.res]['args'] - wrapper.update(commited_args) - result_state = execute(wrapper, 'remove') - - if result_state is state.STATES.success: - wrapper.update(staged_data.get('args', {})) - result_state = execute(wrapper, 'run') - else: - result_state = execute(wrapper, li.action) - - # resource_action return None in case there is no actions - result_state = result_state or state.STATES.success - - commited[li.res] = staged_data - li.state = result_state - - history.add(li) - - if result_state is state.STATES.error: - return + commit(staged.popleft(), resources) def rollback(log_item): @@ -152,7 +166,7 @@ def rollback(log_item): def rollback_uid(uid): - item = next(l for l in state.CL() if l.uuid == uid) + item = next(l for l in state.CL() if l.uid == uid) return rollback(item) From 1aae204447331a410b4fbfa2aa88292580ac7d12 Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Tue, 2 Jun 2015 15:01:16 +0200 Subject: [PATCH 3/4] Add removal of keystone service and endpoint --- library/keystone_service.py | 8 ++++++-- .../actions/remove.yaml | 20 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/library/keystone_service.py b/library/keystone_service.py index f1278673..ab0425c8 100644 --- a/library/keystone_service.py +++ b/library/keystone_service.py @@ -202,11 +202,15 @@ def ensure_endpoint_present(keystone, name, public_url, internal_url, def ensure_service_absent(keystone, name, check_mode): """ Ensure the service is absent""" - raise NotImplementedError() + service = get_service(keystone, name) + keystone.services.delete(service.id) + return True def ensure_endpoint_absent(keystone, name, check_mode): """ Ensure the service endpoint """ - raise NotImplementedError() + endpoint = get_endpoint(keystone, name) + keystone.endpoints.delete(endpoint.id) + return True def dispatch(keystone, name, service_type, description, public_url, diff --git a/resources/keystone_service_endpoint/actions/remove.yaml b/resources/keystone_service_endpoint/actions/remove.yaml index 67218324..dadc9aea 100644 --- a/resources/keystone_service_endpoint/actions/remove.yaml +++ b/resources/keystone_service_endpoint/actions/remove.yaml @@ -1,6 +1,20 @@ - hosts: [{{ ip }}] sudo: yes + vars: + ip: {{ip}} + port: {{port}} + admin_port: {{admin_port}} tasks: - - name: keystone service and endpoint - #TODO: not implemented in module - pause: seconds=1 + - name: remove keystone service and endpoint + keystone_service: + token: {{admin_token}} + name: {{name}} + type: {{type}} + description: {{description}} + publicurl: {{publicurl}} + internalurl: {{internalurl}} + adminurl: {{adminurl}} + region: "RegionOne" + state: present + endpoint: http://{{keystone_host}}:{{keystone_port}}/v2.0/ + From 47583aafcedfe776f7e4b82b38716b765d7e7e7d Mon Sep 17 00:00:00 2001 From: Dmitry Shulyak Date: Wed, 3 Jun 2015 11:09:07 +0200 Subject: [PATCH 4/4] Fixing update procedure and timeouts for services --- cli.py | 4 ---- resources/keystone_service/actions/run.yml | 1 - resources/keystone_service/meta.yaml | 2 +- resources/mariadb_service/actions/run.yml | 1 + solar/solar/core/observer.py | 3 +-- solar/solar/operations.py | 18 ++++++++++-------- solar/solar/state.py | 11 +++++------ 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/cli.py b/cli.py index effc1019..a29fb0dd 100755 --- a/cli.py +++ b/cli.py @@ -178,10 +178,6 @@ def init_changes(): changes.add_command(rollback) - @click.command() - def replay(): - operations.replay_errors() - changes.add_command(replay) def init_cli_connections(): @click.group() diff --git a/resources/keystone_service/actions/run.yml b/resources/keystone_service/actions/run.yml index 93d65f03..484b8312 100644 --- a/resources/keystone_service/actions/run.yml +++ b/resources/keystone_service/actions/run.yml @@ -17,4 +17,3 @@ - {{ config_dir }}:/etc/keystone - name: wait for keystone wait_for: host={{ip}} port={{port}} timeout=20 - diff --git a/resources/keystone_service/meta.yaml b/resources/keystone_service/meta.yaml index 1afa7f4c..0a24811d 100644 --- a/resources/keystone_service/meta.yaml +++ b/resources/keystone_service/meta.yaml @@ -4,7 +4,7 @@ version: 1.0.0 input: image: schema: str! - value: kollaglue/centos-rdo-j-keystone + value: kollaglue/centos-rdo-k-keystone config_dir: schema: str! value: /etc/solar/keystone diff --git a/resources/mariadb_service/actions/run.yml b/resources/mariadb_service/actions/run.yml index da07bf3c..bab8ac12 100644 --- a/resources/mariadb_service/actions/run.yml +++ b/resources/mariadb_service/actions/run.yml @@ -16,3 +16,4 @@ until: result.rc == 0 retries: 30 delay: 0.5 + diff --git a/solar/solar/core/observer.py b/solar/solar/core/observer.py index 27108834..3c3cd718 100644 --- a/solar/solar/core/observer.py +++ b/solar/solar/core/observer.py @@ -17,8 +17,7 @@ class BaseObserver(object): self.receivers = [] def log(self, msg): - pass - #print '{} {}'.format(self, msg) + print '{} {}'.format(self, msg) def __repr__(self): return '[{}:{}] {}'.format(self.attached_to.name, self.name, self.value) diff --git a/solar/solar/operations.py b/solar/solar/operations.py index 2b4591e5..e0006ddf 100644 --- a/solar/solar/operations.py +++ b/solar/solar/operations.py @@ -92,21 +92,23 @@ def commit(li, resources): history = state.CL() staged = state.SL() - wrapper = resources[li.res] + staged_res = resources[li.res] staged_data = patch(li.diff, commited.get(li.res, {})) # TODO(dshulyak) think about this hack for update if li.action == 'update': - commited_args = commited[li.res]['args'] - wrapper.update(commited_args) - result_state = actions.resource_action(wrapper, 'remove') + commited_res = resource.Resource( + staged_res.name, + staged_res.metadata, + commited[li.res]['args'], + commited[li.res]['tags']) + result_state = execute(commited_res, 'remove') if result_state is state.STATES.success: - wrapper.update(staged_data.get('args', {})) - result_state = actions.resource_action(wrapper, 'run') + result_state = execute(staged_res, 'run') else: - result_state = actions.resource_action(wrapper, li.action) + result_state = execute(staged_res, li.action) # resource_action return None in case there is no actions result_state = result_state or state.STATES.success @@ -158,7 +160,7 @@ def rollback(log_item): log_item.res, df, guess_action(commited, staged)) log.add(log_item) - res = resource.wrap_resource(db.get_resource(log_item.res)) + res = db.get_obj_resource(log_item.res) res.update(staged.get('args', {})) res.save() diff --git a/solar/solar/state.py b/solar/solar/state.py index f5befc59..3d1070e5 100644 --- a/solar/solar/state.py +++ b/solar/solar/state.py @@ -75,10 +75,10 @@ class Log(object): def __init__(self, path): self.path = path + items = [] if path in db: - items = db[path] - else: - items = [] + items = db[path] or items + self.items = deque([LogItem( l['uid'], l['res'], l['diff'], l['action'], @@ -120,10 +120,9 @@ class Data(collections.MutableMapping): def __init__(self, path): self.path = path + self.store = {} if path in db: - self.store = db[path] - else: - self.store = {} + self.store = db[path] or self.store def __getitem__(self, key): return self.store[key]