dcell 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. data/.travis.yml +4 -1
  2. data/CHANGES.md +11 -0
  3. data/Gemfile +6 -3
  4. data/LICENSE.txt +1 -1
  5. data/README.md +27 -243
  6. data/benchmarks/receiver.rb +2 -2
  7. data/dcell.gemspec +7 -7
  8. data/explorer/css/bootstrap-responsive.css +686 -0
  9. data/explorer/css/bootstrap-responsive.min.css +12 -0
  10. data/explorer/css/bootstrap.css +3990 -0
  11. data/explorer/css/bootstrap.min.css +689 -0
  12. data/explorer/css/explorer.css +28 -0
  13. data/explorer/ico/favicon.ico +0 -0
  14. data/explorer/img/glyphicons-halflings-white.png +0 -0
  15. data/explorer/img/glyphicons-halflings.png +0 -0
  16. data/explorer/img/logo.png +0 -0
  17. data/explorer/index.html.erb +94 -0
  18. data/explorer/js/bootstrap.js +1726 -0
  19. data/explorer/js/bootstrap.min.js +6 -0
  20. data/lib/dcell.rb +27 -2
  21. data/lib/dcell/celluloid_ext.rb +14 -3
  22. data/lib/dcell/directory.rb +15 -3
  23. data/lib/dcell/explorer.rb +76 -0
  24. data/lib/dcell/future_proxy.rb +32 -0
  25. data/lib/dcell/info_service.rb +117 -0
  26. data/lib/dcell/mailbox_proxy.rb +6 -7
  27. data/lib/dcell/messages.rb +5 -6
  28. data/lib/dcell/node.rb +25 -55
  29. data/lib/dcell/node_manager.rb +81 -0
  30. data/lib/dcell/registries/cassandra_adapter.rb +86 -0
  31. data/lib/dcell/registries/gossip/core.rb +235 -0
  32. data/lib/dcell/registries/gossip_adapter.rb +26 -0
  33. data/lib/dcell/registries/moneta_adapter.rb +0 -7
  34. data/lib/dcell/registries/redis_adapter.rb +0 -31
  35. data/lib/dcell/registries/zk_adapter.rb +1 -39
  36. data/lib/dcell/router.rb +37 -30
  37. data/lib/dcell/rpc.rb +23 -23
  38. data/lib/dcell/server.rb +5 -2
  39. data/lib/dcell/version.rb +1 -1
  40. data/logo.png +0 -0
  41. data/spec/dcell/actor_proxy_spec.rb +4 -0
  42. data/spec/dcell/celluloid_ext_spec.rb +11 -0
  43. data/spec/dcell/directory_spec.rb +1 -1
  44. data/spec/dcell/explorer_spec.rb +17 -0
  45. data/spec/dcell/global_spec.rb +4 -0
  46. data/spec/dcell/registries/gossip_adapter_spec.rb +6 -0
  47. data/spec/spec_helper.rb +14 -7
  48. data/spec/support/registry_examples.rb +0 -18
  49. data/tasks/cassandra.task +84 -0
  50. metadata +55 -35
  51. data/celluloid-zmq/.gitignore +0 -17
  52. data/celluloid-zmq/.rspec +0 -4
  53. data/celluloid-zmq/CHANGES.md +0 -31
  54. data/celluloid-zmq/Gemfile +0 -7
  55. data/celluloid-zmq/README.md +0 -56
  56. data/celluloid-zmq/Rakefile +0 -7
  57. data/celluloid-zmq/celluloid-zmq.gemspec +0 -28
  58. data/celluloid-zmq/lib/celluloid/zmq.rb +0 -36
  59. data/celluloid-zmq/lib/celluloid/zmq/reactor.rb +0 -90
  60. data/celluloid-zmq/lib/celluloid/zmq/sockets.rb +0 -130
  61. data/celluloid-zmq/lib/celluloid/zmq/version.rb +0 -5
  62. data/celluloid-zmq/lib/celluloid/zmq/waker.rb +0 -55
  63. data/celluloid-zmq/spec/celluloid/zmq/actor_spec.rb +0 -6
  64. data/celluloid-zmq/spec/spec_helper.rb +0 -2
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Bootstrap.js by @fat & @mdo
3
+ * Copyright 2012 Twitter, Inc.
4
+ * http://www.apache.org/licenses/LICENSE-2.0.txt
5
+ */
6
+ !function(a){a(function(){"use strict",a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype={constructor:c,close:function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),e.trigger("close"),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger("close").removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()}},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype={constructor:b,setState:function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},toggle:function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")}},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.carousel.defaults,c),this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(){return this.interval=setInterval(a.proxy(this.next,this),this.options.interval),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(){return clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;return!a.support.transition&&this.$element.hasClass("slide")?(this.$element.trigger("slide"),d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")):(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.trigger("slide"),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})),f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=typeof c=="object"&&c;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find(".in"),e;d&&d.length&&(e=d.data("collapse"),d.collapse("hide"),e||d.data("collapse",null)),this.$element[b](0),this.transition("addClass","show","shown"),this.$element[b](this.$element[0][c])},hide:function(){var a=this.dimension();this.reset(this.$element[a]()),this.transition("removeClass","hide","hidden"),this.$element[a](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.$element.trigger(d)};this.$element.trigger(c)[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}"use strict";var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this;if(this.isShown)return;a("body").addClass("modal-open"),this.isShown=!0,this.$element.trigger("show"),g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");!b.$element.parent().length&&b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();if(!this.isShown)return;var e=this;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.trigger("hide").removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);!c.options.delay||!c.options.delay.show?c.show():(c.hoverState="in",setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show))},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);!c.options.delay||!c.options.delay.hide?c.hide():(c.hoverState="out",setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide))},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},setContent:function(){var a=this.tip();a.find(".tooltip-inner").html(this.getTitle()),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove()},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a=(a||"").toString().replace(/(^\s*|\s*$)/,""),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,delay:0,selector:!1,placement:"top",trigger:"hover",title:"",template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'}}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var b=this.tip(),c=this.getTitle(),d=this.getContent();b.find(".popover-title")[a.type(c)=="object"?"append":"html"](c),b.find(".popover-content > *")[a.type(d)=="object"?"append":"html"](d),b.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a=a.toString().replace(/(^\s*|\s*$)/,""),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body").on("click.scroll.data-api",this.selector,d),this.refresh(),this.process()}"use strict",b.prototype={constructor:b,refresh:function(){this.targets=this.$body.find(this.selector).map(function(){var b=a(this).attr("href");return/^#\w/.test(b)&&a(b).length?b:null}),this.offsets=a.map(this.targets,function(b){return a(b).position().top})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.offsets,c=this.targets,d=this.activeTarget,e;for(e=b.length;e--;)d!=c[e]&&a>=b[e]&&(!b[e+1]||a<=b[e+1])&&this.activate(c[e])},activate:function(a){var b;this.activeTarget=a,this.$body.find(this.selector).parent(".active").removeClass("active"),b=this.$body.find(this.selector+'[href="'+a+'"]').parent("li").addClass("active"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],b.trigger({type:"show",relatedTarget:e}),f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(a),this.$element.change(),this.hide()},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c=this,d,e;return this.query=this.$element.val(),this.query?(d=a.grep(this.source,function(a){if(c.matcher(a))return a}),d=this.sorter(d),d.length?this.render(d.slice(0,this.options.items)).show():this.shown?this.hide():this):this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){return a.replace(new RegExp("("+this.query+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keypress,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},keypress:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>'},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery);
@@ -9,19 +9,27 @@ require 'dcell/directory'
9
9
  require 'dcell/mailbox_proxy'
10
10
  require 'dcell/messages'
11
11
  require 'dcell/node'
12
+ require 'dcell/node_manager'
12
13
  require 'dcell/global'
13
14
  require 'dcell/responses'
14
15
  require 'dcell/router'
15
16
  require 'dcell/rpc'
17
+ require 'dcell/future_proxy'
16
18
  require 'dcell/server'
19
+ require 'dcell/info_service'
17
20
 
18
21
  require 'dcell/registries/redis_adapter'
19
22
  require 'dcell/registries/moneta_adapter'
20
23
 
24
+ require 'dcell/registries/gossip/core'
25
+ require 'dcell/registries/gossip_adapter'
26
+
21
27
  require 'dcell/celluloid_ext'
22
28
 
23
29
  # Distributed Celluloid
24
30
  module DCell
31
+ class NotConfiguredError < RuntimeError; end # Not configured yet
32
+
25
33
  DEFAULT_PORT = 7777 # Default DCell port
26
34
  @config_lock = Mutex.new
27
35
 
@@ -46,6 +54,16 @@ module DCell
46
54
 
47
55
  @me = Node.new @configuration['id'], @configuration['addr']
48
56
 
57
+ # Specify the directory server (defaults to me), and add it
58
+ # to the local directory.
59
+ directory = @configuration['directory'] || {}
60
+ directory = directory.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
61
+ directory = {
62
+ 'id' => @configuration['id'],
63
+ 'addr' => @configuration['addr']
64
+ }.merge(directory)
65
+ DCell::Directory.set directory['id'], directory['addr']
66
+
49
67
  registry_adapter = @configuration['registry'][:adapter] || @configuration['registry']['adapter']
50
68
  raise ArgumentError, "no registry adapter given in config" unless registry_adapter
51
69
 
@@ -67,7 +85,10 @@ module DCell
67
85
  end
68
86
 
69
87
  # Obtain the local node ID
70
- def id; @configuration['id']; end
88
+ def id
89
+ raise NotConfiguredError, "please configure DCell with DCell.setup" unless @configuration
90
+ @configuration['id']
91
+ end
71
92
 
72
93
  # Obtain the 0MQ address to the local mailbox
73
94
  def addr; @configuration['addr']; end
@@ -97,6 +118,10 @@ module DCell
97
118
 
98
119
  # DCell's actor dependencies
99
120
  class Group < Celluloid::Group
100
- supervise Server, :as => :dcell_server
121
+ supervise NodeManager, :as => :node_manager
122
+ supervise Server, :as => :dcell_server
123
+ supervise InfoService, :as => :info
101
124
  end
125
+
126
+ Logger = Celluloid::Logger
102
127
  end
@@ -45,7 +45,7 @@ module Celluloid
45
45
  # be reached remotely.
46
46
  def _dump(level)
47
47
  mailbox_id = DCell::Router.register self
48
- "#{mailbox_id}@#{DCell.id}"
48
+ "#{mailbox_id}@#{DCell.id}@#{DCell.addr}"
49
49
  end
50
50
 
51
51
  # Create a mailbox proxy object which routes messages over DCell's overlay
@@ -57,13 +57,24 @@ module Celluloid
57
57
 
58
58
  class SyncCall
59
59
  def _dump(level)
60
- rpc_id = DCell::RPC::Manager.register self
60
+ uuid = DCell::RPC::Manager.register self
61
61
  payload = Marshal.dump([@caller,@method,@arguments,@block])
62
- "#{rpc_id.to_s(16)}@#{DCell.id}:#{payload}"
62
+ "#{uuid}@#{DCell.id}:#{payload}"
63
63
  end
64
64
 
65
65
  def self._load(string)
66
66
  DCell::RPC._load(string)
67
67
  end
68
68
  end
69
+
70
+ class Future
71
+ def _dump(level)
72
+ mailbox_id = DCell::Router.register self
73
+ "#{mailbox_id}@#{DCell.id}@#{DCell.addr}"
74
+ end
75
+
76
+ def self._load(string)
77
+ DCell::FutureProxy._load(string)
78
+ end
79
+ end
69
80
  end
@@ -3,21 +3,33 @@ module DCell
3
3
  module Directory
4
4
  extend self
5
5
 
6
+ @@directory = {}
7
+ @@directory_lock = Mutex.new
8
+
6
9
  # Get the URL for a particular Node ID
7
10
  def get(node_id)
8
- DCell.registry.get_node node_id
11
+ @@directory_lock.synchronize do
12
+ @@directory[node_id]
13
+ end
9
14
  end
10
15
  alias_method :[], :get
11
16
 
12
17
  # Set the address of a particular Node ID
13
18
  def set(node_id, addr)
14
- DCell.registry.set_node node_id, addr
19
+ @@directory_lock.synchronize do
20
+ @@directory[node_id] = addr
21
+ end
15
22
  end
16
23
  alias_method :[]=, :set
17
24
 
18
25
  # List all of the node IDs in the directory
19
26
  def all
20
- DCell.registry.nodes
27
+ @@directory_lock.synchronize { @@directory.keys }
28
+ end
29
+
30
+ # Clear the directory.
31
+ def clear
32
+ @@directory_lock.synchronize { @@directory.clear }
21
33
  end
22
34
  end
23
35
  end
@@ -0,0 +1,76 @@
1
+ require 'dcell'
2
+ require 'reel'
3
+ require 'pathname'
4
+ require 'erb'
5
+
6
+ module DCell
7
+ # Web UI for DCell
8
+ # TODO: rewrite this entire thing with less hax
9
+ class Explorer < Reel::Server
10
+ include Celluloid::IO # FIXME: this should really be unnecessary
11
+ ASSET_ROOT = Pathname.new File.expand_path("../../../explorer", __FILE__)
12
+
13
+ def initialize(host = "127.0.0.1", port = 7778)
14
+ super(host, port, &method(:on_connection))
15
+ end
16
+
17
+ def on_connection(connection)
18
+ request = connection.request
19
+ return unless request
20
+ route connection, request
21
+ end
22
+
23
+ def route(connection, request)
24
+ if request.url == "/"
25
+ path = "index.html"
26
+ else
27
+ path = request.url[%r{^/([a-z0-9\.\-_]+(/[a-z0-9\.\-_]+)*)$}, 1]
28
+ end
29
+
30
+ if !path or path[".."]
31
+ Logger.info "404 Not Found: #{request.path}"
32
+ connection.respond :not_found, "Not found"
33
+ return
34
+ end
35
+
36
+ render_resource connection, path
37
+ end
38
+
39
+ def render_resource(connection, path)
40
+ if node_id = path[%r{^nodes/(.*)$}, 1]
41
+ p node_id
42
+ node = DCell::Node[node_id]
43
+ path = "index.html"
44
+ else
45
+ node = DCell.me
46
+ end
47
+
48
+ asset_path = ASSET_ROOT.join(path)
49
+ if asset_path.exist?
50
+ asset_path.open("r") do |file|
51
+ connection.respond :ok, file
52
+ end
53
+
54
+ Logger.info "200 OK: /#{path}"
55
+ elsif File.exist?(asset_path.to_s + ".erb") and node
56
+ connection.respond :ok, render_template(asset_path.to_s + ".erb", node)
57
+ Logger.info "200 OK: /#{path}"
58
+ else
59
+ connection.respond :not_found, "Not found"
60
+ Logger.info "404 Not Found: /#{path}"
61
+ end
62
+ end
63
+
64
+ def render_template(template, node)
65
+ @node = node
66
+ @info = @node[:info].to_hash
67
+
68
+ template = ERB.new File.read(template, :mode => 'rb')
69
+ template.result(binding)
70
+ end
71
+
72
+ def node_path(node)
73
+ "/nodes/#{node.id}"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ module DCell
2
+ class FutureProxy
3
+ def initialize(mailbox_id,node_id,node_addr)
4
+ @mailbox_id = mailbox_id
5
+ @node_id = node_id
6
+ @node_addr = node_addr
7
+ end
8
+
9
+ def <<(message)
10
+ node = Node[@node_id]
11
+ node = Node.new(@node_id, @node_addr) unless node
12
+ node.send_message! Message::Relay.new(self, message)
13
+ end
14
+
15
+ def _dump(level)
16
+ "#{@mailbox_id}@#{@node_id}@#{@node_addr}"
17
+ end
18
+
19
+ # Loader for custom marshal format
20
+ def self._load(string)
21
+ mailbox_id, node_id, node_addr = string.split("@")
22
+
23
+ if node_id == DCell.id
24
+ future = Router.find(mailbox_id)
25
+ raise "tried to unmarshal dead Celluloid::Future: #{mailbox_id}" unless future
26
+ future
27
+ else
28
+ new(mailbox_id, node_id, node_addr)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,117 @@
1
+ require 'rbconfig'
2
+
3
+ module DCell
4
+ class InfoService
5
+ include Celluloid
6
+ attr_reader :os, :os_version, :hostname, :platform, :distribution
7
+ attr_reader :cpu_arch, :cpu_type, :cpu_vendor, :cpu_speed, :cpu_count
8
+ attr_reader :ruby_version, :ruby_engine, :ruby_platform
9
+
10
+ UPTIME_REGEX = /up ((\d+ days?,)?\s*(\d+:\d+|\d+ \w+)),.*(( \d.\d{2},?){3})/
11
+
12
+ def initialize
13
+ @cpu_arch = RbConfig::CONFIG['host_cpu']
14
+ @os = RbConfig::CONFIG['host_os'][/^[A-Za-z]+/]
15
+
16
+ uname = `uname -a`.match(/\w+ (\w[\w+\.\-]*) ([\w+\.\-]+)/)
17
+ @hostname, @os_version = uname[1], uname[2]
18
+
19
+ @platform = RUBY_PLATFORM
20
+ @ruby_version = RUBY_VERSION
21
+ @ruby_engine = RUBY_ENGINE
22
+
23
+ case os
24
+ when 'darwin'
25
+ cpu_info = `sysctl -n machdep.cpu.brand_string`.match(/^((\w+).*) @ (\d+.\d+)GHz/)
26
+ if cpu_info
27
+ @cpu_type = cpu_info[1]
28
+ @cpu_vendor = cpu_info[2].downcase.to_sym
29
+ @cpu_speed = Float(cpu_info[3])
30
+ end
31
+
32
+ @cpu_count = Integer(`sysctl hw.ncpu`[/\d+/])
33
+ os, release, build = `sw_vers`.scan(/:\t(.*)$/).flatten
34
+ @distribution = "#{os} #{release} (#{build})"
35
+ when 'linux'
36
+ cpu_info = File.read("/proc/cpuinfo")
37
+
38
+ @cpu_vendor = cpu_info[/vendor_id:\s+\s+(Genuine)?(\w+)/, 2]
39
+ model_name = cpu_info.match(/model name\s+:\s+((\w+).*) @ (\d+.\d+)GHz/)
40
+ if model_name
41
+ @cpu_type = model_name[1].gsub(/\s+/, ' ')
42
+ @cpu_vendor = model_name[2].downcase.to_sym
43
+ @cpu_speed = Float(model_name[3])
44
+ end
45
+
46
+ cores = cpu_info.scan(/core id\s+: \d+/).uniq.size
47
+ @cpu_count = cores > 0 ? cores : 1
48
+ @distribution = `lsb_release -d`[/Description:\s+(.*)\s*$/, 1]
49
+ else
50
+ @cpu_type = @cpu_vendor = @cpu_speed = @cpu_count = nil
51
+ end
52
+
53
+ case RUBY_ENGINE
54
+ when 'ruby'
55
+ @ruby_platform = "ruby #{RUBY_VERSION}"
56
+ when 'jruby'
57
+ @ruby_platform = "jruby #{JRUBY_VERSION}"
58
+ when 'rbx'
59
+ @ruby_platform = "rbx #{Rubinius::VERSION}"
60
+ else
61
+ @ruby_platform = RUBY_ENGINE
62
+ end
63
+ end
64
+
65
+ def platform; RUBY_PLATFORM; end
66
+ def ruby_engine; RUBY_ENGINE; end
67
+ def ruby_version; RUBY_VERSION; end
68
+
69
+ def load_averages(uptime_string = `uptime`)
70
+ matches = uptime_string.match(UPTIME_REGEX)
71
+ unless matches
72
+ Logger.warn "Couldn't parse uptime output: #{uptime_string}"
73
+ return
74
+ end
75
+
76
+ averages = matches[4].strip
77
+ averages.split(/,? /).map(&:to_f)
78
+ end
79
+ alias_method :load_average, :load_averages
80
+
81
+ def uptime(uptime_string = `uptime`)
82
+ matches = uptime_string.match(UPTIME_REGEX)
83
+ unless matches
84
+ Logger.warn "Couldn't parse uptime output: #{uptime_string}"
85
+ return
86
+ end
87
+
88
+ uptime = matches[1]
89
+ days_string = uptime[/^(\d+) days/, 1]
90
+ days_string ? Integer(days_string) : 0
91
+ end
92
+
93
+ def to_hash
94
+ uptime_string = `uptime`
95
+
96
+ {
97
+ :os => os,
98
+ :os_version => os_version,
99
+ :hostname => hostname,
100
+ :platform => platform,
101
+ :distribution => distribution,
102
+ :ruby_version => ruby_version,
103
+ :ruby_engine => ruby_engine,
104
+ :ruby_platform => ruby_platform,
105
+ :load_averages => load_averages(uptime_string),
106
+ :uptime => uptime(uptime_string),
107
+ :cpu => {
108
+ :arch => cpu_arch,
109
+ :type => cpu_type,
110
+ :vendor => cpu_vendor,
111
+ :speed => cpu_speed,
112
+ :count => cpu_count
113
+ }
114
+ }
115
+ end
116
+ end
117
+ end
@@ -4,13 +4,12 @@ module DCell
4
4
  class MailboxProxy
5
5
  class InvalidNodeError < StandardError; end
6
6
 
7
- def initialize(node_id, mailbox_id)
7
+ def initialize(node_id, node_addr, mailbox_id)
8
8
  raise ArgumentError, "no mailbox_id given" unless mailbox_id
9
-
9
+
10
10
  @node_id = node_id
11
11
  @node = Node[node_id]
12
- raise ArgumentError, "invalid node_id given" unless @node
13
-
12
+ @node = Node.new(node_id, node_addr) unless @node
14
13
  @mailbox_id = mailbox_id
15
14
  end
16
15
 
@@ -40,12 +39,12 @@ module DCell
40
39
 
41
40
  # Custom marshaller for compatibility with Celluloid::Mailbox marshalling
42
41
  def _dump(level)
43
- "#{@mailbox_id}@#{@node_id}"
42
+ "#{@mailbox_id}@#{@node_id}@#{@node_addr}"
44
43
  end
45
44
 
46
45
  # Loader for custom marshal format
47
46
  def self._load(string)
48
- mailbox_id, node_id = string.split("@")
47
+ mailbox_id, node_id, node_addr = string.split("@")
49
48
 
50
49
  if DCell.id == node_id
51
50
  # If we're on the local node, find the real mailbox
@@ -54,7 +53,7 @@ module DCell
54
53
  mailbox
55
54
  else
56
55
  # Create a proxy to the mailbox on the remote node
57
- DCell::MailboxProxy.new(node_id, mailbox_id)
56
+ DCell::MailboxProxy.new(node_id, node_addr, mailbox_id)
58
57
  end
59
58
  end
60
59
  end
@@ -8,15 +8,14 @@ module DCell
8
8
  @id = object_id
9
9
  end
10
10
 
11
- # Heartbeat messages inform other nodes this node is healthy
12
- class Heartbeat < Message
13
- def initialize
14
- @id = DCell.id
11
+ # Gossip messages contain health and membership information
12
+ class Gossip < Message
13
+ def initialize(peers, data)
14
+ @peers, @data = peers, data
15
15
  end
16
16
 
17
17
  def dispatch
18
- node = DCell::Node[@id]
19
- node.handle_heartbeat if node
18
+ Node.handle_gossip(@peers, @data)
20
19
  end
21
20
  end
22
21
 
@@ -3,73 +3,33 @@ module DCell
3
3
  class Node
4
4
  include Celluloid
5
5
  include Celluloid::FSM
6
- attr_reader :id, :addr
6
+ attr_reader :id, :addr, :timestamp
7
7
 
8
8
  # FSM
9
9
  default_state :disconnected
10
10
  state :shutdown
11
11
  state :disconnected, :to => [:connected, :shutdown]
12
12
  state :connected do
13
- send_heartbeat
14
13
  Celluloid::Logger.info "Connected to #{id}"
15
14
  end
16
15
  state :partitioned do
17
16
  Celluloid::Logger.warn "Communication with #{id} interrupted"
18
17
  end
19
18
 
20
- # Ivars
21
- @nodes = {}
22
- @lock = Mutex.new
23
-
24
- @heartbeat_rate = 5 # How often to send heartbeats in seconds
25
- @heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
26
-
27
19
  # Singleton methods
28
20
  class << self
29
21
  include Enumerable
30
- attr_reader :heartbeat_rate, :heartbeat_timeout
31
-
32
- # Return all available nodes in the cluster
33
- def all
34
- Directory.all.map do |node_id|
35
- find node_id
36
- end
37
- end
38
-
39
- # Iterate across all available nodes
40
- def each
41
- Directory.all.each do |node_id|
42
- yield find node_id
43
- end
44
- end
45
-
46
- # Find a node by its node ID
47
- def find(id)
48
- node = @lock.synchronize { @nodes[id] }
49
- return node if node
22
+ extend Forwardable
50
23
 
51
- addr = Directory[id]
52
-
53
- if addr
54
- if id == DCell.id
55
- node = DCell.me
56
- else
57
- node = Node.new(id, addr)
58
- end
59
-
60
- @lock.synchronize do
61
- @nodes[id] ||= node
62
- @nodes[id]
63
- end
64
- end
65
- end
66
- alias_method :[], :find
24
+ def_delegators "Celluloid::Actor[:node_manager]", :all, :each, :find, :[], :handle_gossip
25
+ def_delegators "Celluloid::Actor[:node_manager]", :gossip_rate, :heartbeat_timeout
67
26
  end
68
27
 
69
28
  def initialize(id, addr)
70
29
  @id, @addr = id, addr
30
+ @timestamp = 0
71
31
  @socket = nil
72
- @heartbeat = nil
32
+ @fresh = true
73
33
 
74
34
  # Total hax to accommodate the new Celluloid::FSM API
75
35
  attach self
@@ -77,7 +37,8 @@ module DCell
77
37
 
78
38
  def finalize
79
39
  transition :shutdown
80
- @socket.close if socket
40
+ @gossip.cancel if @gossip
41
+ @socket.close if @socket
81
42
  end
82
43
 
83
44
  # Obtain the node's 0MQ socket
@@ -137,16 +98,25 @@ module DCell
137
98
  end
138
99
  alias_method :<<, :send_message
139
100
 
140
- # Send a heartbeat message after the given interval
141
- def send_heartbeat
142
- send_message DCell::Message::Heartbeat.new
143
- @heartbeat = after(self.class.heartbeat_rate) { send_heartbeat }
101
+ def tick
102
+ @timestamp += 1
144
103
  end
145
104
 
146
- # Handle an incoming heartbeat for this node
147
- def handle_heartbeat
148
- transition :connected
149
- transition :partitioned, :delay => self.class.heartbeat_timeout
105
+ def fresh?
106
+ @fresh
107
+ end
108
+
109
+ # Handle an incoming timestamp observation for this node
110
+ def handle_timestamp(t)
111
+ @fresh = false if t > 0
112
+ if @timestamp < t
113
+ @timestamp = t
114
+ transition :connected
115
+ transition :partitioned, :delay => self.class.heartbeat_timeout
116
+ unless state == :connected
117
+ Celluloid::Logger.info "Revived node #{id}"
118
+ end
119
+ end
150
120
  end
151
121
 
152
122
  # Friendlier inspection