1: %%============================================================================== 2: %% Copyright 2014 Erlang Solutions Ltd. 3: %% 4: %% Licensed under the Apache License, Version 2.0 (the "License"); 5: %% you may not use this file except in compliance with the License. 6: %% You may obtain a copy of the License at 7: %% 8: %% http://www.apache.org/licenses/LICENSE-2.0 9: %% 10: %% Unless required by applicable law or agreed to in writing, software 11: %% distributed under the License is distributed on an "AS IS" BASIS, 12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13: %% See the License for the specific language governing permissions and 14: %% limitations under the License. 15: %%============================================================================== 16: 17: -module(component_SUITE). 18: -compile([export_all, nowarn_export_all]). 19: 20: -include_lib("escalus/include/escalus.hrl"). 21: -include_lib("common_test/include/ct.hrl"). 22: -include_lib("exml/include/exml.hrl"). 23: -include_lib("exml/include/exml_stream.hrl"). 24: -include_lib("eunit/include/eunit.hrl"). 25: 26: -import(distributed_helper, [add_node_to_cluster/1, 27: mim/0, 28: remove_node_from_cluster/1, 29: require_rpc_nodes/1, 30: start_node/2, 31: stop_node/2]). 32: 33: -import(component_helper, [connect_component/1, 34: connect_component/2, 35: disconnect_component/2, 36: disconnect_components/2, 37: connect_component_subdomain/1, 38: spec/2, 39: get_components/1]). 40: 41: %%-------------------------------------------------------------------- 42: %% Suite configuration 43: %%-------------------------------------------------------------------- 44: 45: all() -> 46: [ 47: {group, xep0114}, 48: {group, subdomain}, 49: {group, hidden_components}, 50: {group, distributed} 51: ]. 52: 53: groups() -> 54: [{xep0114, [], xep0114_tests()}, 55: {subdomain, [], [register_subdomain]}, 56: {hidden_components, [], [disco_with_hidden_component]}, 57: {distributed, [], [register_in_cluster, 58: register_same_on_both 59: %clear_on_node_down TODO: Breaks cover 60: ]}]. 61: 62: suite() -> 63: require_rpc_nodes([mim]) ++ escalus:suite(). 64: 65: xep0114_tests() -> 66: [register_one_component, 67: dirty_disconnect, 68: register_two_components, 69: intercomponent_communication, 70: try_registering_with_wrong_password, 71: try_registering_component_twice, 72: try_registering_existing_host, 73: disco_components, 74: kick_old_component_on_conflict 75: ]. 76: 77: %%-------------------------------------------------------------------- 78: %% Init & teardown 79: %%-------------------------------------------------------------------- 80: 81: init_per_suite(Config) -> 82: Config1 = escalus:init_per_suite(Config), 83: ejabberd_node_utils:init(Config1). 84: 85: end_per_suite(Config) -> 86: escalus:end_per_suite(Config). 87: 88: init_per_group(xep0114, Config) -> 89: Config1 = get_components(Config), 90: escalus:create_users(Config1, escalus:get_users([alice, bob])); 91: init_per_group(subdomain, Config) -> 92: Config1 = get_components(Config), 93: add_domain(Config1), 94: escalus:create_users(Config1, escalus:get_users([alice, astrid])); 95: init_per_group(hidden_components, Config) -> 96: Config1 = get_components(Config), 97: escalus:create_users(Config1, escalus:get_users([alice, bob])); 98: init_per_group(distributed, Config) -> 99: Config1 = get_components(Config), 100: Config2 = add_node_to_cluster(Config1), 101: escalus:create_users(Config2, escalus:get_users([alice, clusterguy])); 102: init_per_group(_GroupName, Config) -> 103: escalus:create_users(Config, escalus:get_users([alice, bob])). 104: 105: end_per_group(subdomain, Config) -> 106: escalus:delete_users(Config, escalus:get_users([alice, astrid])), 107: restore_domain(Config); 108: end_per_group(distributed, Config) -> 109: escalus:delete_users(Config, escalus:get_users([alice, clusterguy])), 110: remove_node_from_cluster(Config); 111: end_per_group(_GroupName, Config) -> 112: escalus:delete_users(Config, escalus:get_users([alice, bob])). 113: 114: init_per_testcase(CaseName, Config) -> 115: escalus:init_per_testcase(CaseName, Config). 116: 117: end_per_testcase(CaseName, Config) -> 118: escalus:end_per_testcase(CaseName, Config). 119: 120: 121: %%-------------------------------------------------------------------- 122: %% Tests 123: %%-------------------------------------------------------------------- 124: dirty_disconnect(Config) -> 125: %% Given one connected component, kill the connection and reconnect 126: CompOpts = ?config(component1, Config), 127: {Component, Addr, _} = connect_component(CompOpts), 128: disconnect_component(Component, Addr), 129: {Component1, Addr, _} = connect_component(CompOpts), 130: disconnect_component(Component1, Addr). 131: 132: register_one_component(Config) -> 133: MongooseMetrics = [{[global, data, xmpp, received, component], changed}, 134: {[global, data, xmpp, sent, component], changed}], 135: PreStoryData = escalus_mongooseim:pre_story([{mongoose_metrics, MongooseMetrics}]), 136: %% Given one connected component 137: CompOpts = ?config(component1, Config), 138: {Component, ComponentAddr, _} = connect_component(CompOpts), 139: escalus_mongooseim:post_story(PreStoryData), 140: verify_component(Config, Component, ComponentAddr), 141: disconnect_component(Component, ComponentAddr). 142: 143: verify_component(Config, Component, ComponentAddr) -> 144: escalus:story(Config, [{alice, 1}], fun(Alice) -> 145: %% When Alice sends a message to the component 146: Msg1 = escalus_stanza:chat_to(ComponentAddr, <<"Hi!">>), 147: escalus:send(Alice, Msg1), 148: %% Then component receives it 149: Reply1 = escalus:wait_for_stanza(Component), 150: escalus:assert(is_chat_message, [<<"Hi!">>], Reply1), 151: 152: %% When component sends a reply 153: Msg2 = escalus_stanza:chat_to(Alice, <<"Oh hi!">>), 154: escalus:send(Component, escalus_stanza:from(Msg2, ComponentAddr)), 155: 156: %% Then Alice receives it 157: Reply2 = escalus:wait_for_stanza(Alice), 158: escalus:assert(is_chat_message, [<<"Oh hi!">>], Reply2), 159: escalus:assert(is_stanza_from, [ComponentAddr], Reply2) 160: end). 161: 162: intercomponent_communication(Config) -> 163: %% Given two connected components 164: CompOpts1 = ?config(component1, Config), 165: CompOpts2 = ?config(component2, Config), 166: {Comp1, CompAddr1, _} = connect_component(CompOpts1), 167: {Comp2, CompAddr2, _} = connect_component(CompOpts2), 168: MongooseMetrics = [{[global, data, xmpp, received, component], changed}, 169: {[global, data, xmpp, sent, component], changed}, 170: {[global, data, xmpp, received, xml_stanza_size], changed}, 171: {[global, data, xmpp, sent, xml_stanza_size], changed}], 172: 173: PreStoryData = escalus_mongooseim:pre_story([{mongoose_metrics, MongooseMetrics}]), 174: %% note that there is no c2s communication happens and 175: %% data.xmpp.*.xml_stanza_size metrics are bounced 176: %% for the components communication 177: 178: %% When the first component sends a message the second component 179: Msg0 = escalus_stanza:chat_to(CompAddr2, <<"intercomponent msg">>), 180: escalus:send(Comp1, escalus_stanza:from(Msg0, CompAddr1)), 181: %% Then the second component receives it 182: Reply0 = escalus:wait_for_stanza(Comp2), 183: escalus:assert(is_chat_message, [<<"intercomponent msg">>], Reply0), 184: 185: escalus_mongooseim:post_story(PreStoryData), 186: 187: disconnect_component(Comp1, CompAddr1), 188: disconnect_component(Comp2, CompAddr2). 189: 190: 191: register_two_components(Config) -> 192: %% Given two connected components 193: CompOpts1 = ?config(component1, Config), 194: CompOpts2 = ?config(component2, Config), 195: {Comp1, CompAddr1, _} = connect_component(CompOpts1), 196: {Comp2, CompAddr2, _} = connect_component(CompOpts2), 197: MongooseMetrics = [{[global, data, xmpp, received, component], changed}, 198: {[global, data, xmpp, sent, component], changed}, 199: {[global, data, xmpp, received, xml_stanza_size], changed}, 200: {[global, data, xmpp, sent, xml_stanza_size], changed}], 201: 202: escalus:story([{mongoose_metrics, MongooseMetrics} | Config], 203: [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 204: %% When the first component sends a message to Alice 205: Msg1 = escalus_stanza:chat_to(Alice, <<"Comp1-2-Alice msg">>), 206: escalus:send(Comp1, escalus_stanza:from(Msg1, CompAddr1)), 207: %% Then she receives it 208: Reply1 = escalus:wait_for_stanza(Alice), 209: escalus:assert(is_chat_message, [<<"Comp1-2-Alice msg">>], Reply1), 210: escalus:assert(is_stanza_from, [CompAddr1], Reply1), 211: 212: %% When the second component sends a message to Bob 213: Msg2 = escalus_stanza:chat_to(Bob, <<"Comp2-2-Bob msg">>), 214: escalus:send(Comp2, escalus_stanza:from(Msg2, CompAddr2)), 215: %% Then he receives it 216: Reply2 = escalus:wait_for_stanza(Bob), 217: escalus:assert(is_chat_message, [<<"Comp2-2-Bob msg">>], Reply2), 218: escalus:assert(is_stanza_from, [CompAddr2], Reply2), 219: 220: %% When Bob sends a reply to the second component 221: Msg3 = escalus_stanza:chat_to(CompAddr2, <<"Bob-2-Comp2 msg">>), 222: escalus:send(Bob, Msg3), 223: %% Then the second component receives it 224: Reply3 = escalus:wait_for_stanza(Comp2), 225: escalus:assert(is_chat_message, [<<"Bob-2-Comp2 msg">>], Reply3), 226: 227: %% WHen Alice sends a reply to the first component 228: Msg4 = escalus_stanza:chat_to(CompAddr1, <<"Alice-2-Comp1 msg">>), 229: escalus:send(Alice, Msg4), 230: %% Then the first component receives it 231: Reply4 = escalus:wait_for_stanza(Comp1), 232: escalus:assert(is_chat_message, [<<"Alice-2-Comp1 msg">>], Reply4) 233: end), 234: 235: disconnect_component(Comp1, CompAddr1), 236: disconnect_component(Comp2, CompAddr2). 237: 238: try_registering_with_wrong_password(Config) -> 239: %% Given a component with a wrong password 240: CompOpts1 = ?config(component1, Config), 241: CompOpts2 = lists:keyreplace(password, 1, CompOpts1, 242: {password, <<"wrong_one">>}), 243: try 244: %% When trying to connect it 245: {Comp, Addr, _} = connect_component(CompOpts2), 246: disconnect_component(Comp, Addr), 247: ct:fail("component connected successfully with wrong password") 248: catch {stream_error, _E} -> 249: %% Then it should fail to do so 250: ok 251: end. 252: 253: try_registering_component_twice(Config) -> 254: %% Given two components with the same name 255: CompOpts1 = ?config(component1, Config), 256: {Comp1, Addr, _} = connect_component(CompOpts1), 257: 258: try 259: %% When trying to connect the second one 260: {Comp2, Addr, _} = connect_component(CompOpts1), 261: disconnect_component(Comp2, Addr), 262: ct:fail("second component connected successfully") 263: catch {stream_error, _} -> 264: %% Then it should fail to do so 265: ok 266: end, 267: 268: disconnect_component(Comp1, Addr). 269: 270: try_registering_existing_host(Config) -> 271: %% Given a external vjud component 272: Component = ?config(vjud_component, Config), 273: 274: try 275: %% When trying to connect it to the server 276: {Comp, Addr, _} = connect_component(Component), 277: disconnect_component(Comp, Addr), 278: ct:fail("vjud component connected successfully") 279: catch {stream_error, _} -> 280: %% Then it should fail since vjud service already exists on the server 281: ok 282: end. 283: 284: %% When conflict_behaviour is kick_old, then: 285: %% - stop old connections by sending stream:error with reason "conflict" 286: kick_old_component_on_conflict(Config) -> 287: CompOpts1 = spec(kicking_component, Config), 288: {Comp1, Addr, _} = connect_component(CompOpts1), 289: 290: %% When trying to connect the second one 291: {Comp2, Addr, _} = connect_component(CompOpts1), 292: 293: %% First connection is disconnected 294: Stanza = escalus:wait_for_stanza(Comp1), 295: escalus:assert(is_stream_error, [<<"conflict">>, <<"">>], Stanza), 296: 297: %% New connection is usable 298: verify_component(Config, Comp2, Addr), 299: 300: disconnect_component(Comp2, Addr). 301: 302: disco_components(Config) -> 303: %% Given two connected components 304: CompOpts1 = ?config(component1, Config), 305: CompOpts2 = ?config(component2, Config), 306: {Comp1, Addr1, _} = connect_component(CompOpts1), 307: {Comp2, Addr2, _} = connect_component(CompOpts2), 308: 309: escalus:story(Config, [{alice, 1}], fun(Alice) -> 310: %% When server asked for the disco features 311: Server = escalus_client:server(Alice), 312: Disco = escalus_stanza:service_discovery(Server), 313: escalus:send(Alice, Disco), 314: 315: %% Then it contains hosts of 2 components 316: DiscoReply = escalus:wait_for_stanza(Alice), 317: escalus:assert(has_service, [Addr1], DiscoReply), 318: escalus:assert(has_service, [Addr2], DiscoReply) 319: end), 320: 321: disconnect_component(Comp1, Addr1), 322: disconnect_component(Comp2, Addr2). 323: 324: %% Verifies that a component connected to the "hidden components" endpoint 325: %% is not discoverable. 326: %% Assumes mod_disco with `{users_can_see_hidden_services, false}` option 327: disco_with_hidden_component(Config) -> 328: %% Given two connected components 329: CompOpts1 = ?config(component1, Config), 330: HCompOpts = spec(hidden_component, Config), 331: {Comp1, Addr1, _} = connect_component(CompOpts1), 332: {HComp, HAddr, _} = connect_component(HCompOpts), 333: 334: escalus:story(Config, [{alice, 1}], fun(Alice) -> 335: %% When server asked for the disco features 336: Server = escalus_client:server(Alice), 337: Disco = escalus_stanza:service_discovery(Server), 338: escalus:send(Alice, Disco), 339: 340: %% Then it contains hosts of component1 and hidden_component is missing 341: DiscoReply = escalus:wait_for_stanza(Alice), 342: escalus:assert(has_service, [Addr1], DiscoReply), 343: escalus:assert(fun(Stanza) -> 344: not escalus_pred:has_service(HAddr, Stanza) 345: end, DiscoReply) 346: end), 347: 348: disconnect_component(Comp1, Addr1), 349: disconnect_component(HComp, HAddr). 350: 351: register_subdomain(Config) -> 352: %% Given one connected component 353: CompOpts1 = ?config(component1, Config), 354: {Comp, Addr, Name} = connect_component_subdomain(CompOpts1), 355: 356: escalus:story(Config, [{alice, 1}, {astrid, 1}], fun(Alice, Astrid) -> 357: %% When Alice asks for service discovery on the server 358: Server1 = escalus_client:server(Alice), 359: Disco1 = escalus_stanza:service_discovery(Server1), 360: escalus:send(Alice, Disco1), 361: 362: %% Then it contains the registered route 363: DiscoReply1 = escalus:wait_for_stanza(Alice), 364: ComponentHost1 = <<Name/binary, ".", Server1/binary>>, 365: escalus:assert(has_service, [ComponentHost1], DiscoReply1), 366: 367: %% When Astrid ask for service discovery on her server 368: Server2 = escalus_client:server(Astrid), 369: false = (Server1 =:= Server2), 370: Disco2 = escalus_stanza:service_discovery(Server2), 371: escalus:send(Astrid, Disco2), 372: 373: %% Then it also contains the registered route 374: DiscoReply2 = escalus:wait_for_stanza(Astrid), 375: ComponentHost2 = <<Name/binary, ".", Server2/binary>>, 376: escalus:assert(has_service, [ComponentHost2], DiscoReply2) 377: 378: end), 379: 380: disconnect_component(Comp, Addr). 381: 382: 383: register_in_cluster(Config) -> 384: %% Given one component connected to the cluster 385: CompOpts1 = ?config(component1, Config), 386: Component1 = connect_component(CompOpts1), 387: {Comp1, Addr1, _} = Component1, 388: CompOpts2 = ?config(component2, Config), 389: Component2 = connect_component(CompOpts2), 390: {Comp2, Addr2, _} = Component2, 391: CompOpts_on_2 = spec(component_on_2, Config), 392: Component_on_2 = connect_component(CompOpts_on_2), 393: {Comp_on_2, Addr_on_2, _} = Component_on_2, 394: 395: escalus:story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, ClusterGuy) -> 396: do_chat_with_component(Alice, ClusterGuy, Component1), 397: do_chat_with_component(Alice, ClusterGuy, Component2), 398: do_chat_with_component(Alice, ClusterGuy, Component_on_2) 399: end), 400: 401: disconnect_component(Comp1, Addr1), 402: disconnect_component(Comp2, Addr2), 403: disconnect_component(Comp_on_2, Addr_on_2), 404: ok. 405: 406: clear_on_node_down(Config) -> 407: CompOpts = ?config(component1, Config), 408: ?assertMatch({_, _, _}, connect_component(CompOpts)), 409: ?assertThrow({stream_error, _}, connect_component(CompOpts)), 410: 411: stop_node(mim(), Config), 412: start_node(mim(), Config), 413: 414: {Comp, Addr, _} = connect_component(CompOpts), 415: disconnect_component(Comp, Addr). 416: 417: do_chat_with_component(Alice, ClusterGuy, Component1) -> 418: {Comp, Addr, Name} = Component1, 419: 420: %% When Alice sends a message to the component 421: Msg1 = escalus_stanza:chat_to(Addr, <<"Hi!">>), 422: escalus:send(Alice, Msg1), 423: %% Then component receives it 424: Reply1 = escalus:wait_for_stanza(Comp), 425: escalus:assert(is_chat_message, [<<"Hi!">>], Reply1), 426: 427: %% When components sends a reply 428: Msg2 = escalus_stanza:chat_to(Alice, <<"Oh hi!">>), 429: escalus:send(Comp, escalus_stanza:from(Msg2, Addr)), 430: 431: %% Then Alice receives it 432: Reply2 = escalus:wait_for_stanza(Alice), 433: escalus:assert(is_chat_message, [<<"Oh hi!">>], Reply2), 434: escalus:assert(is_stanza_from, [Addr], Reply2), 435: 436: %% When ClusterGuy (connected to the other node than component) 437: %% sends a message 438: Msg3 = escalus_stanza:chat_to(Addr, <<"Hello!">>), 439: escalus:send(ClusterGuy, Msg3), 440: %% Then component receives it 441: Reply3 = escalus:wait_for_stanza(Comp), 442: escalus:assert(is_chat_message, [<<"Hello!">>], Reply3), 443: 444: %% When components sends a reply 445: Msg4 = escalus_stanza:chat_to(ClusterGuy, <<"Hola!">>), 446: escalus:send(Comp, escalus_stanza:from(Msg4, Addr)), 447: 448: %% Then ClusterGuy receives it 449: Reply4 = escalus:wait_for_stanza(ClusterGuy), 450: escalus:assert(is_chat_message, [<<"Hola!">>], Reply4), 451: escalus:assert(is_stanza_from, [Addr], Reply4), 452: 453: %% When Alice asks for the disco features 454: Server1 = escalus_client:server(Alice), 455: Disco1 = escalus_stanza:service_discovery(Server1), 456: escalus:send(Alice, Disco1), 457: 458: %% Then it contains host of the service 459: DiscoReply1 = escalus:wait_for_stanza(Alice), 460: escalus:assert(has_service, [Addr], DiscoReply1), 461: 462: %% When ClusterGuy asks for the disco features on her server 463: Server2 = escalus_client:server(ClusterGuy), 464: Disco2 = escalus_stanza:service_discovery(Server2), 465: escalus:send(ClusterGuy, Disco2), 466: 467: %% Then it also contains the service (with the other address though) 468: DiscoReply2 = escalus:wait_for_stanza(ClusterGuy), 469: DistributedAddr = <<Name/binary, ".", Server2/binary>>, 470: escalus:assert(has_service, [DistributedAddr], DiscoReply2). 471: 472: 473: register_same_on_both(Config) -> 474: %% Given two components with the same name 475: %% but not on the same host 476: %% we should be able to register 477: %% and we get two components having the same name and address 478: CompOpts2 = ?config(component2, Config), 479: Component2 = connect_component(CompOpts2), 480: {Comp2, Addr, Name} = Component2, 481: CompOpts_d = spec(component_duplicate, Config), 482: Component_d = connect_component(CompOpts_d), 483: {Comp_d, Addr, Name} = Component_d, 484: 485: escalus:story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, ClusterGuy) -> 486: %% When Alice sends a message to the component 487: Msg1 = escalus_stanza:chat_to(Addr, <<"Hi!">>), 488: escalus:send(Alice, Msg1), 489: %% Then component receives it (on the same node) 490: Reply1 = escalus:wait_for_stanza(Comp2), 491: escalus:assert(is_chat_message, [<<"Hi!">>], Reply1), 492: 493: %% When components sends a reply 494: Msg2 = escalus_stanza:chat_to(Alice, <<"Oh hi!">>), 495: escalus:send(Comp2, escalus_stanza:from(Msg2, Addr)), 496: 497: %% Then Alice receives it 498: Reply2 = escalus:wait_for_stanza(Alice), 499: escalus:assert(is_chat_message, [<<"Oh hi!">>], Reply2), 500: escalus:assert(is_stanza_from, [Addr], Reply2), 501: 502: %% When ClusterGuy (connected to the other node than component) 503: %% sends a message 504: Msg3 = escalus_stanza:chat_to(Addr, <<"Hello!">>), 505: escalus:send(ClusterGuy, Msg3), 506: %% Then component on his node receives it 507: Reply3 = escalus:wait_for_stanza(Comp_d), 508: escalus:assert(is_chat_message, [<<"Hello!">>], Reply3), 509: 510: %% When components sends a reply 511: Msg4 = escalus_stanza:chat_to(ClusterGuy, <<"Hola!">>), 512: escalus:send(Comp_d, escalus_stanza:from(Msg4, Addr)), 513: 514: %% Then ClusterGuy receives it 515: Reply4 = escalus:wait_for_stanza(ClusterGuy), 516: escalus:assert(is_chat_message, [<<"Hola!">>], Reply4), 517: escalus:assert(is_stanza_from, [Addr], Reply4), 518: 519: %% When Alice asks for the disco features 520: Server1 = escalus_client:server(Alice), 521: Disco1 = escalus_stanza:service_discovery(Server1), 522: escalus:send(Alice, Disco1), 523: 524: %% Then it contains host of the service 525: DiscoReply1 = escalus:wait_for_stanza(Alice), 526: escalus:assert(has_service, [Addr], DiscoReply1), 527: 528: %% When ClusterGuy asks for the disco features on her server 529: Server2 = escalus_client:server(ClusterGuy), 530: Disco2 = escalus_stanza:service_discovery(Server2), 531: escalus:send(ClusterGuy, Disco2), 532: 533: %% Then it also contains the same service 534: DiscoReply2 = escalus:wait_for_stanza(ClusterGuy), 535: escalus:assert(has_service, [Addr], DiscoReply2) 536: 537: end), 538: disconnect_components([Comp2, Comp_d], Addr), 539: ok. 540: 541: %%-------------------------------------------------------------------- 542: %% Helpers 543: %%-------------------------------------------------------------------- 544: add_domain(Config) -> 545: Hosts = {hosts, "\"localhost\", \"sogndal\""}, 546: ejabberd_node_utils:backup_config_file(Config), 547: ejabberd_node_utils:modify_config_file([Hosts], Config), 548: ejabberd_node_utils:restart_application(mongooseim), 549: ok. 550: 551: restore_domain(Config) -> 552: ejabberd_node_utils:restore_config_file(Config), 553: ejabberd_node_utils:restart_application(mongooseim), 554: Config. 555: 556: 557: %%-------------------------------------------------------------------- 558: %% Stanzas 559: %%-------------------------------------------------------------------- 560: 561: 562: cluster_users() -> 563: AllUsers = ct:get_config(escalus_users), 564: [proplists:lookup(alice, AllUsers), proplists:lookup(clusterguy, AllUsers)].