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

Feature/xep 357 push notifications #1178

Merged
merged 16 commits into from
Feb 18, 2017

Conversation

rslota
Copy link
Contributor

@rslota rslota commented Feb 3, 2017

This PR features XEP-0357 implementation. All needed informations can be found in mod_push.md. Right now only PM and MUCLight messages are supported.
Also pleas note that this code requires a custom PubSub node that will handle notifications from this module and pass them to external Push Notifications Service.

Tis PR also adds worker_pool as pooler used in mod_push. This pooler is faster and more configurable than others like poolboy. There are several pending PRs that use this pooler, so maybe someday we are gonna switch fully to worker_pool.

@@ -130,9 +131,9 @@ route(From, To, Packet) ->
case (catch do_route(From, To, Packet)) of
{'EXIT', Reason} ->
?ERROR_MSG("error when routing from=~ts to=~ts in module=~p, reason=~p, packet=~ts, stack_trace=~p",
Copy link

Choose a reason for hiding this comment

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

According to Elvis:

Line 133 is too long: ?ERROR_MSG("error when routing from=~ts to=~ts in module=~p, reason=~p, packet=~ts, stack_trace=~p",.

@@ -819,7 +820,7 @@ get_user_present_pids(LUser, LServer) ->

-spec get_user_present_resources(LUser :: ejabberd:user(),
LServer :: ejabberd:server()
) -> [{priority(), binary()}].
) -> [{priority(), binary()}].
get_user_present_resources(LUser, LServer) ->
Ss = ?SM_BACKEND:get_sessions(LUser, LServer),
Copy link

Choose a reason for hiding this comment

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

According to Elvis:

Don't use macros (like SM_BACKEND on line 825) as module names.

Node :: mod_push:pubsub_node()) -> ok | {error, Reason :: term()}.
disable(User, undefined, undefined) ->
delete(key(User));
disable(User, undefined, undefined) ->
Copy link

Choose a reason for hiding this comment

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

According to Compiler:

Warning: this clause cannot match because a previous clause at line 61 always matches

%%% @end
%%%-------------------------------------------------------------------
-module(mod_push_plugin).
-behavior(mod_push_plugin).
Copy link

Choose a reason for hiding this comment

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

According to Compiler:

Warning: behaviour mod_push_plugin undefined

@rslota rslota force-pushed the feature/xep-357-push_notifications branch from da3e73c to 69b126a Compare February 3, 2017 14:20
@@ -819,7 +821,7 @@ get_user_present_pids(LUser, LServer) ->

-spec get_user_present_resources(LUser :: ejabberd:user(),
LServer :: ejabberd:server()
) -> [{priority(), binary()}].
) -> [{priority(), binary()}].
get_user_present_resources(LUser, LServer) ->
Ss = ?SM_BACKEND:get_sessions(LUser, LServer),
Copy link

Choose a reason for hiding this comment

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

According to Elvis:

Don't use macros (like SM_BACKEND on line 826) as module names.

@rslota rslota force-pushed the feature/xep-357-push_notifications branch from 69b126a to b4e2c1b Compare February 10, 2017 11:06
### Module Description
This module implements [XEP-0357: Push Notifications](https://xmpp.org/extensions/xep-0357.html).
It enables a service that notifies `PubSub` of user's choice about every message that he could
miss while being offline. There are two control stanzas that clinet may send to this module:
Copy link
Member

Choose a reason for hiding this comment

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

Typo: clinet

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks!

do_route_no_resource(<<"broadcast">>, _, From, To, Packet) ->
% Backward compatibility
% Backward compatibility
Copy link
Member

Choose a reason for hiding this comment

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

Indent glitch? :)

@@ -744,7 +746,7 @@ route_message(From, To, Packet) ->
%% Route messages to all priority that equals the max, if
%% positive
fun({Prio, Pid}) when Prio == Priority ->
% we will lose message if PID is not alive
% we will lose message if PID is not alive
Copy link
Member

Choose a reason for hiding this comment

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

Indent glitch? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... My formatter (emacs) assumes that comments with single '%' have fixed indentation because of reasons. I'm gonna revert all those changes.


% Backward compatibility
% Backward compatibility
Copy link
Member

Choose a reason for hiding this comment

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

Indent glitch? :)

@@ -976,19 +978,19 @@ user_resources(UserStr, ServerStr) ->
sm_backend(Backend) ->
lists:flatten(
["-module(ejabberd_sm_backend).
-export([backend/0]).
-export([backend/0]).
Copy link
Member

Choose a reason for hiding this comment

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

Broken indent

_ ->
0
end.
case mongoose_metrics:get_metric_value(global, ?UNIQUE_COUNT_CACHE) of
Copy link
Member

Choose a reason for hiding this comment

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

Broken indent.

<<"chat">> ->
jid:to_binary(jid:to_bare(jid:to_lower(From)));
<<"groupchat">> ->
LResource
Copy link
Contributor

Choose a reason for hiding this comment

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

In this case LResource is the message sender. Important information in case of groupchat notifications is also the room name. How this will be delivered in the push notification?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. Room name will be stripped and the user will get only sender JID. If you think it shouldn't, I'll add LUser to sender_id.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think it's mandatory to deliver the room name for groupchat notifications.

Copy link
Member

Choose a reason for hiding this comment

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

I agree that room's luser should be included as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done :)

filter messages that shall not be published to `PubSub` node.


[aws-virtual-host]: https://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me why do we need this and following links in this doc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a copy-paste error. Removed.

-define(NS_XDATA, <<"jabber:x:data">>).
-define(NS_PUBSUB_PUB_OPTIONS, <<"http://jabber.org/protocol/pubsub#publish-options">>).
-define(PUSH_FORM_TYPE, <<"urn:xmpp:push:summary">>).
-define(MUCHOST, <<"muclight.localhost">>).
Copy link
Contributor

Choose a reason for hiding this comment

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

We should avoid hardcoding the main XMPP domain served by the target server. You can easily read it from tests configuration via:

ct:get_config({hosts, mim, domain})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

-define(PUSH_FORM_TYPE, <<"urn:xmpp:push:summary">>).
-define(MUCHOST, <<"muclight.localhost">>).

-define(MUC_HOST, <<"muc.localhost">>).
Copy link
Contributor

Choose a reason for hiding this comment

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

Another hardcoded localhost.

init_per_group(muclight_msg_notifications, Config) ->
Host = ct:get_config({hosts, mim, domain}),
dynamic_modules:start(Host, mod_push, ?PUSH_OPTS),
dynamic_modules:start(<<"localhost">>, mod_muc_light,
Copy link
Contributor

Choose a reason for hiding this comment

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

Hardcoded localhost while one line above domain read from config was used.

start(Host, Opts) ->
?INFO_MSG("mod_push starting on host ~p", [Host]),

ok = application:ensure_started(worker_pool),
Copy link
Member

Choose a reason for hiding this comment

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

I'd avoid command that could start worker_pool here, as it should be done during VM boot. I think it would be better to check for wpool:start_sup_pool return value to not only verify that worker_pool is running but also that pool creation was indeed successful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

{group, muclight_msg_notifications}
].

groups() ->
Copy link
Contributor

Choose a reason for hiding this comment

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

This set of test cases looks impressive. I have two general comments:

  1. This sute is not executed on travis as it is not added to default.spec
  2. I encourage to run all groups in parallel. escalus:fresh_story is your friend here, also you can take a look at sm_SUITE or any other suite using fresh_story.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'm gonna try enabling parallel groups.

ok.

-spec stop(Host :: ejabberd:server()) -> ok.
stop(Host) ->
Copy link
Member

Choose a reason for hiding this comment

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

Lacks worker pool shutdown.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

form = Form
}.

-spec key(ejabberd:jid()) -> key() | no_return().
Copy link
Member

Choose a reason for hiding this comment

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

It can't have no_return(), can it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good question, indeed :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, there was explicit throw in early implementation but its gone now, I just forgot to change spec.


-record(route, {from, to, packet}).

-define(assertReceivedMatch(Expect), ?assertReceivedMatch(Expect, 0)).
Copy link
Member

Choose a reason for hiding this comment

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

Is it used anywhere actually?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, sorry about that.

Server = escalus_client:server(Alice),
escalus:send(Alice, escalus_stanza:disco_info(Server)),
Stanza = escalus:wait_for_stanza(Alice),
try
Copy link
Member

@fenek fenek Feb 13, 2017

Choose a reason for hiding this comment

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

What is the reason for using these try..catches in test cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess for better error readability. I saw something like that in other test suite in disco checks so I thought thats a thing. If its not I'm just gonna remove it.

Copy link
Member

Choose a reason for hiding this comment

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

Those checks in other suite were probably implemented when escalus wasn't reporting full stanza log upon error yet. Now there is no need to manually log stanza. Which suite is it BTW?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are several try..catch (11) like that in mam_SUITE and muc_SUITE.
Anyway, I'm gonna remove it from here.

end]),
Config.

-spec rpc(M :: atom(), F :: atom(), A :: [term()]) -> term().
Copy link
Member

Choose a reason for hiding this comment

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

Already implemented in https://github.com/esl/escalus/blob/master/src/escalus_ejabberd.erl#L55

Unless it's about the timeout value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No its not, just forgot about that.

Cookie = escalus_ct:get_config(ejabberd_cookie),
escalus_ct:rpc_call(Node, M, F, A, 10000, Cookie).

create_room(Room, [Owner | Members]) ->
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll try this way. Somehow I prefer initialising via XMPP.

start_publish_listener(Config) ->
TestCasePid = self(),
rpc(meck, new, [ejabberd_router, [no_link, passthrough]]),
rpc(meck, expect,
Copy link
Member

Choose a reason for hiding this comment

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

I don't like the tampering with ejabberd_router here. Have you considered declaring e.g. push.localhost as JID in enable request and registering CT process as domain handler with register_route?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest I just wasn't aware there is such option. I'll try that, thanks.

transaction(F).

-spec transaction(fun(() -> any())) -> Result :: any().
transaction(F) ->
Copy link
Member

Choose a reason for hiding this comment

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

Do we need transactions in case of mod_push? I believe that conflicts or race conditions won't occur without them and it's always less work for Mnesia. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It just reduces the change of race condition with poorly written clients. If no one have an objection I will remove those transactions.

-spec should_publish(From :: ejabberd:jid(), To :: ejabberd:jid(), Packet :: jlib:xmlel()) ->
boolean().
should_publish(_From, To = #jid{luser = LUser, lserver = LServer}, _Packet) ->
?WARNING_MSG("should_publish ~p", [{LUser, LServer,

Choose a reason for hiding this comment

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

According to Elvis:

Missing space right "," on line 53

should_publish(_From, To = #jid{luser = LUser, lserver = LServer}, _Packet) ->
?WARNING_MSG("should_publish ~p", [{LUser, LServer,
ejabberd_users:does_user_exist(LUser, LServer),
catch lists:max(ejabberd_sm:get_user_present_pids(LUser, LServer))}]),

Choose a reason for hiding this comment

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

According to Elvis:

Line 55 is too long: catch lists:max(ejabberd_sm:get_user_present_pids(LUser, LServer))}]),
.

%%% @end
%%%-------------------------------------------------------------------
-module(mod_push_plugin).
-behavior(mod_push_plugin).

Choose a reason for hiding this comment

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

According to Compiler:

Warning: behaviour mod_push_plugin undefined

@rslota rslota force-pushed the feature/xep-357-push_notifications branch 3 times, most recently from 76f5a03 to 3791c48 Compare February 17, 2017 13:37
@rslota rslota force-pushed the feature/xep-357-push_notifications branch from 3791c48 to a34a6ea Compare February 17, 2017 15:15
@michalwski michalwski merged commit 67aeaf6 into master Feb 18, 2017
@michalwski michalwski deleted the feature/xep-357-push_notifications branch February 18, 2017 10:50
@michalwski
Copy link
Contributor

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants