Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose bind leader directly #4836

Merged
merged 3 commits into from
Apr 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions components/sup/doc/render_context_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,17 @@
"properties": {
"first": {
"description": "The first member of this service group. If the group is running in a leader topology, this will also be the leader.",
"$deprecated": "Since 0.56.0; if you want the leader, use `leader` explicitly. 'first' isn't deterministic, either, so you can just use `members[0]` instead",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not up on the whole handlebars world, but I remember seeing something about the syntax changing to something like members.[0]. Does that apply here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baumanj Habitat's current syntax is foo[0], but the Handlebars library we use recently fixed itself to use the proper foo.[0] syntax. We can't upgrade our Handlebars library right now, though, because it would break any template that has the foo[0] syntax. So, until then, we have to keep the old way around 😦

"$ref": "#/definitions/svc_member"
},
"leader": {
"description": "The current leader of this service group, if running in a leader topology",
"$since": "0.56.0",
"oneOf": [
{ "$ref": "#/definitions/svc_member" },
{ "type": "null" }
]
},
"members": {
"description": "All members of the service group, across the entire ring. Includes all liveness states!",
"type": "array",
Expand All @@ -510,6 +519,7 @@
},
"required": [
"first",
"leader",
"members"
],
"additionalProperties": false
Expand Down
148 changes: 109 additions & 39 deletions components/sup/src/templating/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,13 +417,15 @@ impl<'a> Binds<'a> {
#[derive(Clone, Debug, Serialize)]
struct BindGroup<'a> {
first: Option<SvcMember<'a>>,
leader: Option<SvcMember<'a>>,
members: Vec<SvcMember<'a>>,
}

impl<'a> BindGroup<'a> {
fn new(group: &'a CensusGroup) -> Self {
BindGroup {
first: select_first(group),
leader: group.leader().map(|m| SvcMember::from_census_member(m)),
members: group
.members()
.iter()
Expand Down Expand Up @@ -610,20 +612,24 @@ fn select_first(census_group: &CensusGroup) -> Option<SvcMember> {
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use std::io::Read;
use json;
use serde_json;

use std::collections::BTreeMap;
use std::fs;
use valico::json_schema;
use std::io::{Read, Write};
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;

use serde_json;
use json;
use tempdir::TempDir;
use manager::service::config::PackageConfigPaths;
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::collections::BTreeMap;
use valico::json_schema;

use butterfly::rumor::service::SysInfo;
use hcore::package::PackageIdent;

use manager::service::Cfg;
use manager::service::config::PackageConfigPaths;
use templating::TemplateRenderer;

/// Asserts that `json_string` is valid according to our render
/// context JSON schema.
Expand Down Expand Up @@ -671,7 +677,8 @@ JSON:
serde_json::from_str(&raw_schema).expect("Could not parse schema as JSON");
let mut scope = json_schema::scope::Scope::new();
// NOTE: using `false` instead of `true` allows us to use
// `$comment` keys
// `$comment` keyword, as well as our own `$deprecated` and
// `$since` keywords.
let schema = scope.compile_and_return(parsed_schema, false).expect(
"Could not compile the schema",
);
Expand Down Expand Up @@ -745,6 +752,37 @@ two = 2

////////////////////////////////////////////////////////////////////////

/// Create a basic SvcMember struct for use in tests
fn default_svc_member<'a>() -> SvcMember<'a> {
let ident = PackageIdent::new("core", "test_pkg", Some("1.0.0"), Some("20180321150416"));
SvcMember {
member_id: Cow::Owned("MEMBER_ID".into()),
pkg: Cow::Owned(Some(ident)),
application: Cow::Owned(None),
environment: Cow::Owned(None),
service: Cow::Owned("foo".into()),
group: Cow::Owned("default".into()),
org: Cow::Owned(None),
persistent: Cow::Owned(true),
leader: Cow::Owned(false),
follower: Cow::Owned(false),
update_leader: Cow::Owned(false),
update_follower: Cow::Owned(false),
election_is_running: Cow::Owned(false),
election_is_no_quorum: Cow::Owned(false),
election_is_finished: Cow::Owned(false),
update_election_is_running: Cow::Owned(false),
update_election_is_no_quorum: Cow::Owned(false),
update_election_is_finished: Cow::Owned(false),
sys: Cow::Owned(SysInfo::new()),
alive: Cow::Owned(true),
suspect: Cow::Owned(false),
confirmed: Cow::Owned(false),
departed: Cow::Owned(false),
cfg: Cow::Owned(BTreeMap::new() as toml::value::Table),
}
}

/// Just create a basic RenderContext that could be used in tests.
///
/// If you want to modify parts of it, it's easier to change
Expand Down Expand Up @@ -814,39 +852,13 @@ two = 2
let (_tmp_dir, test_pkg) = new_test_pkg();
let cfg = Cfg::new(&test_pkg, None).expect("create config");

use butterfly::rumor::service::SysInfo;
let sys_info = SysInfo::new();

// TODO (CM): just create a toml table directly
let mut svc_member_cfg = BTreeMap::new();
svc_member_cfg.insert("foo".into(), "bar".into());

let me = SvcMember {
member_id: Cow::Owned("MEMBER_ID".into()),
pkg: Cow::Owned(Some(ident.clone())),
application: Cow::Owned(None),
environment: Cow::Owned(None),
service: Cow::Owned("foo".into()),
group: Cow::Owned("default".into()),
org: Cow::Owned(None),
persistent: Cow::Owned(true),
leader: Cow::Owned(false),
follower: Cow::Owned(false),
update_leader: Cow::Owned(false),
update_follower: Cow::Owned(false),
election_is_running: Cow::Owned(false),
election_is_no_quorum: Cow::Owned(false),
election_is_finished: Cow::Owned(false),
update_election_is_running: Cow::Owned(false),
update_election_is_no_quorum: Cow::Owned(false),
update_election_is_finished: Cow::Owned(false),
sys: Cow::Owned(sys_info),
alive: Cow::Owned(true),
suspect: Cow::Owned(false),
confirmed: Cow::Owned(false),
departed: Cow::Owned(false),
cfg: Cow::Owned(svc_member_cfg as toml::value::Table),
};
let mut me = default_svc_member();
me.pkg = Cow::Owned(Some(ident.clone()));
me.cfg = Cow::Owned(svc_member_cfg as toml::value::Table);

let svc = Svc {
service_group: Cow::Owned(group),
Expand All @@ -862,6 +874,7 @@ two = 2
let mut bind_map = HashMap::new();
let bind_group = BindGroup {
first: Some(me.clone()),
leader: None,
members: vec![me.clone()],
};
bind_map.insert("foo".into(), bind_group);
Expand All @@ -876,6 +889,20 @@ two = 2
}
}

/// Render the given template string using the given context,
/// returning the result. This can help to verify that
/// RenderContext data are accessible to users in the way we
/// expect.
fn render(template_content: &str, ctx: &RenderContext) -> String {
let mut renderer = TemplateRenderer::new();
renderer
.register_template_string("testing", template_content)
.expect("Could not register template content");
renderer.render("testing", ctx).expect(
"Could not render template",
)
}

////////////////////////////////////////////////////////////////////////

/// Reads a file containing real rendering context output from an
Expand Down Expand Up @@ -923,4 +950,47 @@ two = 2
assert_valid(&j);
}

#[test]
fn no_leader_renders_correctly() {
let ctx = default_render_context();

// Just make sure our default context is set up how this test
// is expecting
assert!(ctx.bind.0.get("foo").unwrap().leader.is_none());

let output = render(
"{{#if bind.foo.leader}}THERE IS A LEADER{{else}}NO LEADER{{/if}}",
&ctx,
);

assert_eq!(output, "NO LEADER");
}

#[test]
fn leader_renders_correctly() {
let mut ctx = default_render_context();

// Let's create a new leader, with a custom member_id
let mut svc_member = default_svc_member();
svc_member.member_id = Cow::Owned("deadbeefdeadbeefdeadbeefdeadbeef".into());

// Set up our own bind with a leader
let mut bind_map = HashMap::new();
let bind_group = BindGroup {
first: Some(svc_member.clone()),
leader: Some(svc_member.clone()),
members: vec![svc_member.clone()],
};
bind_map.insert("foo".into(), bind_group);
let binds = Binds(bind_map);
ctx.bind = binds;

// This template should reveal the member_id of the leader
let output = render(
"{{#if bind.foo.leader}}{{bind.foo.leader.member_id}}{{else}}NO LEADER{{/if}}",
&ctx,
);

assert_eq!(output, "deadbeefdeadbeefdeadbeefdeadbeef");
}
}
1 change: 1 addition & 0 deletions components/sup/tests/fixtures/sample_render_context.json
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@
"update_follower": false,
"update_leader": false
},
"leader": null,
"members": [
{
"alive": true,
Expand Down