crowbar/crowbar-ha

View on GitHub
crowbar_framework/app/assets/javascripts/barclamps/pacemaker/application.js

Summary

Maintainability
C
7 hrs
Test Coverage
/**
 * Copyright 2011-2013, Dell
 * Copyright 2013-2014, SUSE LINUX Products GmbH
 *
 * 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.
 */

;(function($, doc, win) {
  'use strict';

  function StonithNodeAgents(el, options) {
    this.root = $(el);
    this.html = {
      table_row: '<tr data-id="{0}" data-alias="{1}"><td>{1}</td><td><input type="text" class="form-control" value="{2}"/></td></tr>'
    };

    this.options = $.extend(
      {
        attr_type: 'string',
        attr_name: 'params',
        attr_writer: function(val) { return val; },
        attr_reader: function(val) { return val; },
        storage: '#proposal_attributes',
        deployment_storage: '#proposal_deployment',
        path: 'stonith/per_node/nodes',
        watchedRoles: ['pacemaker-cluster-member', 'pacemaker-remote']
      },
      options
    );

    this.initialize();
  }

  StonithNodeAgents.prototype._ignore_event = function(evt, data) {
    var self = this;

    var row_id = 'tr[data-id="{0}"]'.format(data.id);
    var row    = this.root.find(row_id)

    if (self.options.watchedRoles.indexOf(data.role) == -1) { return true; }
    if (evt.type == 'nodeListNodeAllocated' && row.length > 0) { return true; }
    if (evt.type == 'nodeListNodeUnallocated' && row.length == 0) { return true; }

    return false;
  };

  StonithNodeAgents.prototype.initialize = function() {
    var self = this;

    // Update nodes that have been moved around in deployment
    self.updateNodesFromDeployment();
    // Render what we already have
    self.renderAgentParams();
    // And start listening on changes
    self.registerEvents();
  };

  StonithNodeAgents.prototype.registerEvents = function() {
    var self = this;

    // Update JSON on input changes
    this.root.find('tbody tr').live('change', function(evt) {
      var elm = $(this);
      var id  = elm.data('id');
      var key = '{0}/{1}'.format(id, self.options.attr_name);
      var val = self.options.attr_writer(elm.find('input').val());

      self.writeJson(key, val, self.options.attr_type);
    });

    // Append new table row and update JSON on node alloc
    $(document).on('nodeListNodeAllocated', this.root, function(evt, data) {
      if (self._ignore_event(evt, data)) { return; }

      $(this).find('tbody').append(self.html.table_row.format(data.id, self._node_name_to_alias(data.id), ''));

      var key = '{0}/{1}'.format(data.id, self.options.attr_name);
      var val = self.options.attr_writer("");

      self.writeJson(key, val, self.options.attr_type);
      self.sortAgentParams();
    });

    // Remove the table row and update JSON on node dealloc
    $(document).on('nodeListNodeUnallocated', this.root, function(evt, data) {
      if (self._ignore_event(evt, data)) { return; }

      self.root.find('[data-id="{0}"]'.format(data.id)).remove();
      self.removeJson(data.id, null, "map");
    });
  };

  StonithNodeAgents.prototype.updateNodesFromDeployment = function() {
    var self = this;

    var agent_params = self.retrieveAgentParams();

    // Get a membership hash on both sides
    var existing_nodes = {};
    var deployed_nodes = {};

    $.each(agent_params, function(node, value) { existing_nodes[node] = true; });

    $.each(self.options.watchedRoles, function(index, role) {
      var role_path  = 'elements/{0}'.format(role);
      var role_nodes = $(self.options.deployment_storage).readJsonAttribute(role_path, {});
      $.each(role_nodes, function(index, node) { deployed_nodes[node] = true; });
    });

    // Then update all those nodes which have been removed from deployment
    for (var existing_node in existing_nodes) {
       if (!deployed_nodes[existing_node]) { self.removeJson(existing_node, null, "map"); }
    }
    // and those which have been added
    for (var deployed_node in deployed_nodes) {
       if (!existing_nodes[deployed_node]) {
         var key = '{0}/{1}'.format(deployed_node, self.options.attr_name);
         var val = self.options.attr_writer("");
         self.writeJson(key, val, self.options.attr_type);
       }
    }
  };

  StonithNodeAgents.prototype._node_name_to_alias = function(name) {
    var node_info = this.root.data('nodes')[name];
    return !!node_info ? node_info.alias : name.split('.')[0]
  };

  StonithNodeAgents.prototype.sortAgentParams = function() {
    var self = this;

    var rows = [];
    $.each(this.root.find('tbody tr'), function(index, tr) {
      rows.push([$(tr).data('id'), $(tr).data('alias'), $(tr).find('input').val()]);
    });

    // Sort by node alias
    rows.sort(function(a, b) {
      if (a[1] > b[1]) { return 1;  }
      if (a[1] < b[1]) { return -1; }
      return 0;
    });

    var params = $.map(rows, function(row) {
      var encoded_input = Handlebars.Utils.escapeExpression(row[2]);
      return self.html.table_row.format(row[0], row[1], encoded_input);
    });
    this.root.find('tbody').html(params.join(''));
  };

  // Initial render
  StonithNodeAgents.prototype.renderAgentParams = function() {
    var self = this;

    var params = $.map(self.retrieveAgentParams(), function(value, node_id) {
      var encoded_value = Handlebars.Utils.escapeExpression(self.options.attr_reader(value[self.options.attr_name]));
      return self.html.table_row.format(node_id, self._node_name_to_alias(node_id), encoded_value);
    });

    this.root.find('tbody').html(params.join(''));
    self.sortAgentParams();
  };

  // FIXME: these could be refactored into a common agent
  StonithNodeAgents.prototype.retrieveAgentParams = function() {
    return $(this.options.storage).readJsonAttribute(
      this.options.path,
      {}
    );
  };

  StonithNodeAgents.prototype.writeJson = function(key, value, type) {
    return $(this.options.storage).writeJsonAttribute(
      '{0}/{1}'.format(
        this.options.path,
        key
      ),
      value,
      type
    );
  };

  StonithNodeAgents.prototype.removeJson = function(key, value, type) {
    return $(this.options.storage).removeJsonAttribute(
      '{0}/{1}'.format(
        this.options.path,
        key
      ),
      value,
      type
    );
  };

  $.fn.stonithNodeAgents = function(options) {
    return this.each(function() {
      new StonithNodeAgents(this, options);
    });
  };
}(jQuery, document, window));

function update_no_quorum_policy(evt, init) {
  var no_quorum_policy_el = $('#crm_no_quorum_policy');
  var non_forced_policy = no_quorum_policy_el.data('non-forced');
  var was_forced_policy = no_quorum_policy_el.data('is-forced');
  var members = $('#pacemaker-cluster-member').children().length;

  if (non_forced_policy == undefined) {
    non_forced_policy = "stop";
  }

  if (evt != undefined) {
    // 'nodeListNodeAllocated' is fired after the element has been added, so
    // nothing to do. However, 'nodeListNodeUnallocated' is fired before the
    // element is removed, so we need to fix the count.
    if (evt.type == 'nodeListNodeUnallocated') { members -= 1; }
  }

  if (members > 1) {
    if (was_forced_policy) {
      no_quorum_policy_el.val(non_forced_policy);
      no_quorum_policy_el.removeData('non-forced');
    }
    no_quorum_policy_el.data('is-forced', false)
    no_quorum_policy_el.removeAttr('disabled');
  } else {
    if (!init && !was_forced_policy) {
      no_quorum_policy_el.data('non-forced', no_quorum_policy_el.val());
    }
    no_quorum_policy_el.data('is-forced', true)
    no_quorum_policy_el.val("ignore");
    no_quorum_policy_el.attr('disabled', 'disabled');
  }
}

function cb_corosync_ring_delete()
{
  ring_index = $(this).data("ringid");

  // delete the ring entry from the attributes JSON
  rings = $('#proposal_attributes').readJsonAttribute('corosync/rings', {});
  rings.splice(ring_index, 1);
  $('#proposal_attributes').writeJsonAttribute('corosync/rings', rings);

  $('#ring-index-' + ring_index).hide('slow', function() {
    redisplay_rings();
  });

  return false;
}

function attach_events()
{
  $('#corosync-rings [data-change]').updateAttribute();

  $('.corosync-ring-delete').on('click', cb_corosync_ring_delete);
  $('#corosync-rings [data-hideit]').trigger('change');
  $('#corosync-rings [data-showit]').trigger('change');
}

function detach_events()
{
  $('#corosync-rings [data-change]').off('change keyup');
  $('.corosync-ring-delete').off('click');
}

var corosync_ring_template;
var max_corosync_rings = 2;
function redisplay_rings()
{
  if (!corosync_ring_template) {
    corosync_ring_template = Handlebars.compile(
      $('#ring-entries').html()
    );

    Handlebars.registerHelper("inc", function(value) {
      return parseInt(value) + 1;
    });
  }

  rings = $('#proposal_attributes').readJsonAttribute('corosync/rings', {});

  // Render forms for ring list
  $('#corosync-rings').replaceWith(
    corosync_ring_template({
      "entries": rings,
      "at_min_entries": rings.length == 1
    })
  );

  $.map($('#corosync_transport option'), function(option) {
    if (!option.selected) {
      $('.ring_{0}_container'.format(option.value)).hide();
    }
  });

  if (rings.length >= max_corosync_rings) {
    $('#ring-add').hide('slow');
  } else {
    $('#ring-add').show('slow');
  }

  // refresh data-change handlers
  detach_events();
  attach_events();
}

$(document).ready(function($) {
  $('#stonith_per_node_container').stonithNodeAgents();
  $('#stonith_sbd_container').stonithNodeAgents({
    path:'stonith/sbd/nodes',
    attr_name:'devices',
    attr_type:'seq',
    attr_reader:function(val) { return val.join(', '); },
    attr_writer:function(val) { return val.replace(/ /g, ',').replace(/,+/g, ',').replace(/,$/, '').split(','); }
  });

  // FIXME: apparently using something else than
  // $('#stonith_per_node_container') breaks the per-node table :/
  $(document).on('nodeListNodeAllocated', function(evt, data) {
    update_no_quorum_policy(evt, false)
  });
  $(document).on('nodeListNodeUnallocated', function(evt, data) {
    update_no_quorum_policy(evt, false)
  });

  update_no_quorum_policy(undefined, true)

  if ($.queryString.attr_raw != "true") {
    redisplay_rings();
  }

  $('#add-ring-button').click(function() {
    var new_ring = {
      'network': $('#corosync_rings_index_network').val(),
      'mcast_addr': $('#corosync_rings_index_mcast_addr').val(),
      'mcast_port': 5405
    };

    rings = $('#proposal_attributes').readJsonAttribute('corosync/rings', {});
    rings.push(new_ring);
    $('#proposal_attributes').writeJsonAttribute('corosync/rings', rings);

    // Reset field entries
    $('#corosync_rings_index_network').val('');
    $('#corosync_rings_index_mcast_addr').val('');

    redisplay_rings();
  });
});