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