Skip to content

Commit c4ce790

Browse files
authored
Merge pull request #52 from zookzook/command-monitoring
Command monitoring
2 parents 55dcdfd + e3c4681 commit c4ce790

35 files changed

+1133
-228
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ plt_core_path/*.plt
1010
.idea/
1111
*.iml
1212
*.code-workspace
13+
data/

.travis.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1+
dist: bionic
12
sudo: required
23
language: elixir
34

4-
elixir:
5-
- 1.8
6-
- 1.9
5+
elixir: 1.10.2
6+
otp_release: 22.0
7+
8+
addons:
9+
apt:
10+
packages:
11+
- python3
12+
- python3-pip
713

814
install:
9-
- wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-${MONGODB}.tgz
15+
- wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1804-4.4.0-rc0.tgz
1016
- mkdir ${PWD}/mongodb
11-
- tar xzf mongodb-linux-x86_64-${MONGODB}.tgz --strip-components=1 -C ${PWD}/mongodb
17+
- tar xzf mongodb-linux-x86_64-ubuntu1804-4.4.0-rc0.tgz --strip-components=1 -C ${PWD}/mongodb
1218

1319
before_script:
20+
- pyenv global 3.6
21+
- pip3 install --upgrade pip
22+
- pip3 install mtools[all]
1423
- export PATH=${PWD}/mongodb/bin/:$PATH
15-
- bash ./start_mongo.bash
16-
- mkdir ${PWD}/mongodb/data
17-
- ${PWD}/mongodb/bin/mongod --dbpath ${PWD}/mongodb/data --logpath ${PWD}/mongodb/mongodb.log --fork
18-
19-
env:
20-
matrix:
21-
- MONGODB=3.2.20
22-
- MONGODB=3.4.16
23-
- MONGODB=3.6.6
24-
- MONGODB=ubuntu1604-v4.2-latest
24+
- mlaunch init --setParameter enableTestCommands=1 --replicaset --name "rs_1"
2525

2626
script:
2727
- mix local.hex --force

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 0.7.0
2+
3+
* Enhancements
4+
* refactored event notification system
5+
* added support for retryable reads and writes
6+
* refactored the test cases
7+
* now using mtools for a MongoDB deployment in the travis ci environment
8+
* travis ci uses only the latest MongoDB version [The failCommand](https://github.com/mongodb/mongo/wiki/The-%22failCommand%22-fail-point)
9+
110
## 0.6.5
211

312
* Enhancements

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
## Features
1010

11-
* Supports MongoDB versions 3.2, 3.4, 3.6, 4.0, 4.2
11+
* Supports MongoDB versions 3.2, 3.4, 3.6, 4.0, 4.2, 4.4
1212
* Connection pooling ([through DBConnection 2.x](https://github.com/elixir-ecto/db_connection))
1313
* Streaming cursors
1414
* Performant ObjectID generation
@@ -20,6 +20,9 @@
2020
* Support for bulk writes ([See](https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#write))
2121
* support for driver sessions ([See](https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst))
2222
* support for driver transactions ([See](https://github.com/mongodb/specifications/blob/master/source/transactions/transactions.rst))
23+
* support for command monitoring ([See](https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst))
24+
* support for retryable reads ([See](https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst))
25+
* support for retryable writes ([See](https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst))
2326

2427
## Data representation
2528

@@ -368,7 +371,21 @@ Using `$in`
368371
Mongo.find(:mongo, "users", %{email: %{"$in" => ["[email protected]", "[email protected]"]}})
369372
```
370373

371-
## Testing
374+
## Testing and Travis CI
375+
376+
The `travis.yml` file uses only the latest MongoDB. It creates a replica set of three nodes and disables the SSL test case. If you want to
377+
run the test cases against other MongoDB deployments or older versions, you can use the [mtools](https://github.com/rueckstiess/mtools) for deployment and run the test cases locally:
378+
379+
### Example
380+
381+
```bash
382+
pyenv global 3.6
383+
pip3 install --upgrade pip
384+
pip3 install mtools[all]
385+
export PATH=to-your-mongodb/bin/:$PATH
386+
mlaunch init --setParameter enableTestCommands=1 --replicaset --name "rs_1"
387+
mix test --exclude ssl --exclude socket
388+
```
372389

373390
The SSL test suite is enabled by default. You have two options. Either exclude
374391
the SSL tests or enable SSL on your Mongo server.

lib/mongo.ex

Lines changed: 153 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ defmodule Mongo do
5050
import Mongo.Utils
5151
import Mongo.WriteConcern
5252

53-
require Logger
54-
5553
use Bitwise
5654
use Mongo.Messages
5755
alias Mongo.Query
@@ -60,6 +58,10 @@ defmodule Mongo do
6058
alias Mongo.UrlParser
6159
alias Mongo.Session
6260
alias Mongo.ReadPreference
61+
alias Mongo.Events
62+
alias Mongo.Events.CommandSucceededEvent
63+
alias Mongo.Events.CommandFailedEvent
64+
alias Mongo.Error
6365

6466
@timeout 15000 # 5000
6567

@@ -379,26 +381,52 @@ defmodule Mongo do
379381

380382
end
381383

384+
def admin_command(topology_pid, cmd) do
385+
with {:ok, doc} <- issue_command(topology_pid, cmd, :write, database: "admin", retryable_writes: false) do
386+
{:ok, doc}
387+
end
388+
end
389+
382390
@doc """
383391
This function is very fundamental.
384392
"""
385-
def issue_command(topology_pid, cmd, type, opts) do
393+
def issue_command(topology_pid, cmd, :read, opts) do
386394

387-
new_cmd = case type do
388-
:read -> ReadPreference.add_read_preference(cmd, opts)
389-
:write -> cmd
390-
end
395+
new_cmd = ReadPreference.add_read_preference(cmd, opts)
391396

392-
Logger.debug("issue_command: #{inspect type} #{inspect new_cmd}")
397+
## check, if retryable reads are enabled
398+
opts = Mongo.retryable_reads(opts)
393399

394-
with {:ok, session} <- Session.start_implicit_session(topology_pid, type, opts),
400+
with {:ok, session} <- Session.start_implicit_session(topology_pid, :read, opts),
395401
result <- exec_command_session(session, new_cmd, opts),
396402
:ok <- Session.end_implict_session(topology_pid, session) do
403+
case result do
404+
{:error, error} ->
405+
case Error.should_retry_read(error, cmd, opts) do
406+
true -> issue_command(topology_pid, cmd, :read, Keyword.put(opts, :read_counter, 2))
407+
false -> {:error, error}
408+
end
409+
_other -> result
410+
end
411+
else
412+
{:new_connection, _server} ->
413+
:timer.sleep(1000)
414+
issue_command(topology_pid, cmd, :read, opts)
415+
end
416+
end
417+
def issue_command(topology_pid, cmd, :write, opts) do
418+
419+
## check, if retryable reads are enabled
420+
opts = Mongo.retryable_writes(opts, acknowledged?(cmd[:writeConcerns]))
421+
422+
with {:ok, session} <- Session.start_implicit_session(topology_pid, :write, opts),
423+
result <- exec_command_session(session, cmd, opts),
424+
:ok <- Session.end_implict_session(topology_pid, session) do
397425
result
398426
else
399427
{:new_connection, _server} ->
400428
:timer.sleep(1000)
401-
issue_command(topology_pid, cmd, type, opts)
429+
issue_command(topology_pid, cmd, :write, opts)
402430
end
403431
end
404432

@@ -688,33 +716,61 @@ defmodule Mongo do
688716
@doc false
689717
@spec exec_command_session(GenServer.server, BSON.document, Keyword.t) :: {:ok, BSON.document | nil} | {:error, Mongo.Error.t}
690718
def exec_command_session(session, cmd, opts) do
691-
692-
Logger.debug("Executing cmd with session: #{inspect cmd}")
693-
694-
with {:ok, conn, cmd} <- Session.bind_session(session, cmd),
695-
{:ok, _cmd, doc} <- DBConnection.execute(conn, %Query{action: :command}, [cmd], defaults(opts)),
696-
doc <- Session.update_session(session, doc, opts),
697-
{:ok, doc} <- check_for_error(doc) do
719+
with {:ok, conn, new_cmd} <- Session.bind_session(session, cmd),
720+
{:ok, _cmd, {doc, event}} <- DBConnection.execute(conn, %Query{action: :command}, [new_cmd], defaults(opts)),
721+
doc <- Session.update_session(session, doc, opts),
722+
{:ok, doc} <- check_for_error(doc, event) do
698723
{:ok, doc}
724+
else
725+
{:error, error} ->
726+
## todo update Topology
727+
case Error.should_retry_write(error, cmd, opts) do
728+
true ->
729+
with :ok <- Session.select_server(session, opts) do
730+
exec_command_session(session, cmd, Keyword.put(opts, :write_counter, 2))
731+
end
732+
false -> {:error, error}
733+
end
699734
end
700735

701736
end
702737

703738
@doc false
704739
@spec exec_command(GenServer.server, BSON.document, Keyword.t) :: {:ok, BSON.document | nil} | {:error, Mongo.Error.t}
705740
def exec_command(conn, cmd, opts) do
706-
707-
Logger.debug("Executing cmd: #{inspect cmd}")
708-
709-
with {:ok, _cmd, doc} <- DBConnection.execute(conn, %Query{action: :command}, [cmd], defaults(opts)),
710-
{:ok, doc} <- check_for_error(doc) do
741+
with {:ok, _cmd, {doc, event}} <- DBConnection.execute(conn, %Query{action: :command}, [cmd], defaults(opts)),
742+
{:ok, doc} <- check_for_error(doc, event) do
711743
{:ok, doc}
712744
end
713745

714746
end
715747

716-
defp check_for_error(%{"ok" => ok} = response) when ok == 1, do: {:ok, response}
717-
defp check_for_error(doc), do: {:error, Mongo.Error.exception(doc)}
748+
defp check_for_error(%{"ok" => ok} = response, {event, duration}) when ok == 1 do
749+
Events.notify(%CommandSucceededEvent{
750+
reply: response,
751+
duration: duration,
752+
command_name: event.command_name,
753+
request_id: event.request_id,
754+
operation_id: event.operation_id,
755+
connection_id: event.connection_id
756+
}, :commands)
757+
{:ok, response}
758+
end
759+
defp check_for_error(doc, {event, duration}) do
760+
761+
error = Mongo.Error.exception(doc)
762+
763+
Events.notify(%CommandFailedEvent{
764+
failure: error,
765+
duration: duration,
766+
command_name: event.command_name,
767+
request_id: event.request_id,
768+
operation_id: event.operation_id,
769+
connection_id: event.connection_id
770+
}, :commands)
771+
772+
{:error, error}
773+
end
718774

719775
@doc """
720776
Returns the wire version of the database
@@ -1149,6 +1205,22 @@ defmodule Mongo do
11491205
end
11501206
end
11511207

1208+
@doc """
1209+
Convenient function that drops the database `name`.
1210+
"""
1211+
@spec drop_database(GenServer.server, String.t) :: :ok | {:error, Mongo.Error.t}
1212+
def drop_database(topology_pid, name \\ nil)
1213+
def drop_database(topology_pid, nil) do
1214+
with {:ok, _} <- Mongo.issue_command(topology_pid, [dropDatabase: 1], :write, []) do
1215+
:ok
1216+
end
1217+
end
1218+
def drop_database(topology_pid, name) do
1219+
with {:ok, _} <- Mongo.issue_command(topology_pid, [dropDatabase: 1], :write, [database: name]) do
1220+
:ok
1221+
end
1222+
end
1223+
11521224
@doc """
11531225
Getting Collection Names
11541226
"""
@@ -1168,6 +1240,63 @@ defmodule Mongo do
11681240
|> Stream.map(fn coll -> coll["name"] end)
11691241
end
11701242

1243+
@doc """
1244+
In case of retryable reads are enabled, the keyword `:read_counter` is added with the value of 1.
1245+
1246+
In other cases like
1247+
1248+
* `:retryable_reads` is false or nil
1249+
* `:session` is nil
1250+
* `:read_counter` is nil
1251+
1252+
the `opts` is unchanged
1253+
1254+
## Example
1255+
1256+
iex> Mongo.retryable_reads([retryable_reads: true])
1257+
[retryable_reads: true, read_counter: 1]
1258+
1259+
"""
1260+
def retryable_reads(opts) do
1261+
case opts[:read_counter] do
1262+
nil -> case opts[:retryable_reads] == true && opts[:session] == nil do
1263+
true -> opts ++ [read_counter: 1]
1264+
false -> opts
1265+
end
1266+
_other -> opts
1267+
end
1268+
end
1269+
1270+
@doc """
1271+
In case of retryable writes are enabled, the keyword `:write_counter` is added with the value of 1.
1272+
1273+
In other cases like
1274+
1275+
* `:retryable_writes` is false or nil
1276+
* `:session` is nil
1277+
* `:write_counter` is nil
1278+
1279+
the `opts` is unchanged
1280+
1281+
## Example
1282+
1283+
iex> Mongo.retryable_writes([retryable_writes: true], true)
1284+
[retryable_writes: true, write_counter: 1]
1285+
1286+
"""
1287+
def retryable_writes(opts, true) do
1288+
case opts[:write_counter] do
1289+
nil -> case Keyword.get(opts, :retryable_writes, true) == true && opts[:session] == nil do
1290+
true -> opts ++ [write_counter: 1]
1291+
false -> opts
1292+
end
1293+
_other -> opts
1294+
end
1295+
end
1296+
def retryable_writes(opts, false) do
1297+
Keyword.put(opts, :retryable_writes, false)
1298+
end
1299+
11711300
defp get_stream(topology_pid, cmd, opts) do
11721301
Mongo.Stream.new(topology_pid, cmd, opts)
11731302
end

lib/mongo/app.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Mongo.App do
77
children = [
88
worker(Mongo.IdServer, []),
99
worker(Mongo.PBKDF2Cache, []),
10-
worker(:gen_event, [local: Mongo.Events])
10+
supervisor(Registry, [:duplicate, :events_registry])
1111
]
1212

1313
opts = [strategy: :one_for_one, name: Mongo.Supervisor]

0 commit comments

Comments
 (0)