From 93b29843691f182b4e2b9dbee906ba4bf9590385 Mon Sep 17 00:00:00 2001 From: Vincent Untz Date: Wed, 4 Jan 2017 10:01:19 +0100 Subject: [PATCH] crowbar: Create event hook infrastructure There are several cases where we have events that should trigger some activity in some other part of Crowbar. For instance: - when the public name of a node is saved, it may impact the endpoint of an OpenStack service - when the public name of the VIP of haproxy is changed, it impacts the endpoint of OpenStack services - when the keystone proposal is applied, we may want to reapply all proposals that depend on keystone What we need for this is the ability to notify about the events in the rails application and then to dispatch the notifications to hooks listen listening to them that will decide if some action should be triggered. The main reason we didn't have this in the past is that we likely don't want to do that in the foreground of the rails application. But now that we have delayed_job, we can send the notifications and run the hooks in the background. In this commit, we add the simple infrastructure about notifications and hooks: - the events are defined with a name and a hash that contains the details of the event. The structure of the hash depends on the event. - we have two events that are generated and notified: node_changed (when a node is saved and some specific attributes have been changed) and proposal_applied (when a proposal is successfully applied). - a simple dispatcher exists that simply connects the hooks to the events. - the hooks only exist for service objects for the time being; a service object simply needs to have a event_hook method to register the hook, and will need to filter for the events it cares about. The signature of event_hook is as follows: def event_hook(role, event, details) It could be argued that the hooks should be registered for some specific events (hence moving the filter to the event dispatcher), but it's not worth the complexity for now. We could add many more events but for the time being, we only add the events that we know are useful. --- crowbar_framework/app/models/node.rb | 22 ++++++++- .../app/models/service_object.rb | 7 +++ .../lib/crowbar/event_dispatcher.rb | 48 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 crowbar_framework/lib/crowbar/event_dispatcher.rb diff --git a/crowbar_framework/app/models/node.rb b/crowbar_framework/app/models/node.rb index 0eff2ab9ce..310798fa08 100644 --- a/crowbar_framework/app/models/node.rb +++ b/crowbar_framework/app/models/node.rb @@ -16,6 +16,7 @@ # require "chef/mixin/deep_merge" +require "set" require "timeout" require "open3" @@ -37,6 +38,7 @@ def initialize(node) end # deep clone of @role.default_attributes, used when saving node @attrs_last_saved = @role.default_attributes.deep_dup + @attributes_changed = Set.new @node = node end @@ -67,7 +69,10 @@ def availability_zone def availability_zone=(value) @node["crowbar_wall"] ||= {} @node["crowbar_wall"]["openstack"] ||= {} - @node["crowbar_wall"]["openstack"]["availability_zone"] = value + if @node["crowbar_wall"]["openstack"]["availability_zone"] != value + @node["crowbar_wall"]["openstack"]["availability_zone"] = value + @attributes_changed.add("availability_zone") + end end def intended_role @@ -257,6 +262,7 @@ def validate_public_name(value, unique_check = true) def update_public_name(value) unless value.nil? crowbar["crowbar"]["public_name"] = value + @attributes_changed.add("public_name") end end @@ -619,6 +625,20 @@ def _remove_elements_from_node(old, new, from_node) @attrs_last_saved = @role.default_attributes.deep_dup Rails.logger.debug("Done saving node: #{@node.name} - #{crowbar_revision}") + + unless @attributes_changed.empty? + Rails.logger.debug("Notifying about saved node: #{@node.name}") + + details = { + node: @node.name, + attributes: @attributes_changed + } + Crowbar::EventDispatcher.trigger_hooks(:node_changed, details) + + @attributes_changed = Set.new + + Rails.logger.debug("Done notifying about saved node: #{@node.name}") + end end def destroy diff --git a/crowbar_framework/app/models/service_object.rb b/crowbar_framework/app/models/service_object.rb index 9e24c5d0c0..9245de55cd 100644 --- a/crowbar_framework/app/models/service_object.rb +++ b/crowbar_framework/app/models/service_object.rb @@ -1372,6 +1372,13 @@ def apply_role(role, inst, in_queue, bootstrap = false) proposal.save unless roles_to_remove.empty? update_proposal_status(inst, "success", "") + + details = { + barclamp: @bc_name, + instance: inst + } + Crowbar::EventDispatcher.trigger_hooks(:proposal_applied, details) + [200, {}] rescue StandardError => e @logger.fatal("apply_role: Uncaught exception #{e.message} #{e.backtrace.join("\n")}") diff --git a/crowbar_framework/lib/crowbar/event_dispatcher.rb b/crowbar_framework/lib/crowbar/event_dispatcher.rb new file mode 100644 index 0000000000..d38c91d607 --- /dev/null +++ b/crowbar_framework/lib/crowbar/event_dispatcher.rb @@ -0,0 +1,48 @@ +# +# Copyright 2017, SUSE +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module Crowbar + class EventDispatcher + class << self + def trigger_hooks(event, details) + BarclampCatalog.barclamps.keys.each do |barclamp| + begin + cls = ServiceObject.get_service(barclamp) + rescue NameError + # catalog may contain barclamps which don't have services + next + end + + next unless cls.method_defined?(:event_hook) + + service = cls.new(Rails.logger) + + proposals = Proposal.where(barclamp: barclamp) + proposals.each do |proposal| + role = proposal.role + next if role.nil? + begin + service.event_hook(role, event, details) + rescue StandardError => e + raise "Error while executing event hook of #{barclamp} for #{event}: #{e.message}" + end + end + end + end + handle_asynchronously :trigger_hooks + end + end +end