Skip to content

Commit

Permalink
Merge pull request 2600hz#11 from kageds/4.3.acdc
Browse files Browse the repository at this point in the history
4.3.acdc
  • Loading branch information
alanrevans authored Sep 23, 2020
2 parents d1ae993 + 0980e99 commit fd9354a
Show file tree
Hide file tree
Showing 67 changed files with 7,927 additions and 2,877 deletions.
9 changes: 9 additions & 0 deletions applications/acdc/doc/acdc_agent_maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## SUP-able functions

| Function | Arguments | Description |
| -------- | --------- | ----------- |
| `acct_restart/1` | `(AccountId)` | |
| `acct_status/1` | `(AccountId)` | |
| `agent_restart/2` | `(AccountId,AgentId)` | |
| `agent_status/2` | `(AccountId,AgentId)` | |
| `status/0` | | |
29 changes: 24 additions & 5 deletions applications/acdc/doc/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ The interplay between AMQP messages, the agent process, and the agent FSM proces
<- member_connect_resp --
```

An agent will respond to all connect requests while in the *ready* state. The acdc_agent will pass along the `member_connect_req` to the FSM. If the FSM is in the *ready* state, the FSM will pass the request JSON to the agent process to send the `member_connect_resp` message. If the FSM is in any other state, the `member_connect_req` payload will be ignored.
An agent will respond to all connect requests while in the *ready* state. The acdc_agent will pass along the `member_connect_req` to the FSM. If the FSM is in the *ready* state, the FSM will pass the request JSON to the agent process to send the `member_connect_resp` message. If the FSM is in any other state, the `member_connect_req` payload will be ignored.

> Member Connect Win
Depending on the Queue strategy (ring_all, round robin), 1 or more agents that responded may receive the `member_connect_win` message

> Member Connect Win and same Agent answers the member call
```asciiart
AMQP Erlang
Expand All @@ -79,7 +81,24 @@ An agent will respond to all connect requests while in the *ready* state. The ac
{ready}
```

When an agent receives a `member_connect_win` while in the *ready* state, the FSM will instruct the agent process to bridge to the agent's endpoint(s) and enter the *ringing* state. The agent process binds to the call events and attempts the bridge. Once the bridge is established, the FSM will instruct the agent process to send a `member_connect_accepted` and move to the *answered* state. On receiving a hangup event, the FSM will instruct the agent process to unbind from call events and will start the wrapup timer and enter the *wrapup* state. Once the timer fires, the FSM will return to the *ready* state.
> Member Connect Win and different Agent answers the member call
```asciiart
AMQP Erlang
[Queue] [Agent] [AgentFSM]
{ready}
---- member_connect_win ---->
-- member_connect_win --> ok
<- bridge_member --------
{ringing}
{bind}
-- call_event ---------->
-- member_connect_satisfied -->
-- member_connect_satisfied -->
{ready}
```

When an agent receives a `member_connect_win` while in the *ready* state, the FSM will instruct the agent process to bridge to the agent's endpoint(s) and enter the *ringing* state. The agent process binds to the call events and attempts the bridge. Once the bridge is established, the FSM will instruct the agent process to send a `member_connect_accepted` and move to the *answered* state. On receiving a hangup event, the FSM will instruct the agent process to unbind from call events and will start the wrapup timer and enter the *wrapup* state. Once the timer fires, the FSM will return to the *ready* state. If another answers the member call then the agent process will receive a `member_connect_satisfied` and move back tp the *ready* state.

> Member Connect Win (fail to bridge)
Expand Down Expand Up @@ -226,10 +245,10 @@ When `acdc_init` starts up (last child of acdc_sup), it iterates through each ac
acdc_init:
foreach acct in accts
foreach queue in acct
acdc_queues_sup:new(AcctId, QueueId)
acdc_queues_sup:new(AccountId, QueueId)
```

`acdc_queues_sup` will start an `acdc_queue_sup` process to represent the AcctId/QueueId combo. The child list of the `acdc_queue_sup` has two entries: `acdc_queue_manager` and `acdc_queue_workers_sup`.
`acdc_queues_sup` will start an `acdc_queue_sup` process to represent the AccountId/QueueId combo. The child list of the `acdc_queue_sup` has two entries: `acdc_queue_manager` and `acdc_queue_workers_sup`.

`acdc_queue_manager` handles receiving updates from config docs about the queue, member calls to the queue, etc.
`acdc_queue_workers_sup` handles managing actual member_call processing units. Its really a pool of `member_call workers`. This allows us to utilize AMQP's ack/nack features to handle worker crashes and not lose the member call, but still process multiple calls at once (meaning, if we have a queue with 10 agents, and 5 members call in, 5 phones had better start ringing).
Expand Down
96 changes: 96 additions & 0 deletions applications/acdc/doc/features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
### Features

#### Annoucements

audio file played to caller to annouce position in the queue and estimated wait time.

add this object to the queue document
```
"announcements": {
"wait_time_announcements_enabled": true,
"position_announcements_enabled": true,
"interval": 30
},
```

#### Queue Strategies

`round_robin`
`ring_all`
`most_idle`
`sbrr`

#### Agent Priorities

1 to 128 : Higher priority agents will be called 1st

#### Call Priorities

Higher priority calls can jump the queue
1) callflow can set "priority" in data to an integer value, higher the number the higher the priority
2) and/or the incoming call can have a custom variable <<"Call-Priority">> again an integer


#### Member Callback

Member can have the option of being called back when they reach the head of the queue rather than waiting in line


#### Agent Pause

An agent can be on a break and pause for set time either via the API or via a feature code


#### Early wrapup

An Agent can make himself available before the wrapup period via the API


#### Agent Availability check callflow

Example callflow:

```
"flow": {
"module": "acdc_agent_availability",
"data": {"id": "9a218da18b8104c888f0d47d946ffac0"},
"children": {
"available": {
"data": {
"id": "9a218da18b8104c888f0d47d946ffac0"
},
"module": "acdc_member",
"children": {}
},
"unavailable": {
"data": {
"id": "/system_media/en-us%2Fqueue-no_agents_available"
},
"module": "play",
"children": {}
}
}
}
```

#### Average wait time check callflow

Example callflow:

```
"module": "acdc_wait_time",
"data": {"id": "9a218da18b8104c888f0d47d946ffac0"},
"children": {
"_": {
"data": {"id": "9a218da18b8104c888f0d47d946ffac0"},
"module": "acdc_member",
"children": {}
},
"60": {
"data": {"id": "/system_media/en-us%2Fqueue-no_agents_available"},
"module": "play",
"children": {}
}
}
```

23 changes: 23 additions & 0 deletions applications/acdc/doc/issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### Known Issues

#### Round Robin strategy

In a multi node/zone cluster round robin doesn't work as an enduser would expect.

The problem is that each node in the cluster has an independent Queue Manager process which picks the next suitable agent. Queue Managers only manage the node and so calls handled by other nodes in the cluster are not taken into account when selecting the next agent.

Consider this scenario:

Lets say we have a round robin queue Q1 with 4 agents (A1, A2, A3, A4) all with the same priority and there are 2 nodes in a cluster N1 and N2.

when we start up Q1 will have a Queue Manager process on N1 and a Queue Manager process on N2. Each manager creates its own agent queue AQ1 and AQ2. Lets assume the Agents are initally added to the AQs in the same order [A1,A2,A3,A4]

Now a call comes into Q1 and is handled by N1. The Queue Manager on N1 looks at the head of AQ1 and selects agent A1 for taking the call, A1 is then put at the back so AQ1 on N1 becomes [A2,A3,A4,A1] and agent A1's device starts ringing.

A1 finishes the call.

A 2nd call comes in and this time is handled by N2. AQ2 on N2 is still [A1,A2,A3,A4] and so A1 is selected again, hardly round robin.

If, however, the 2nd call was again handled by N1 then everything would be as expected as A2 is at the head of AQ1.

So the problem is how to tell N2 to move A1 to the back so that it matches N1. A possible solution would be to broadcast a federated agent_win message on the AMQP bus that all Queue Managers listen to and they update thier AQ. Another possible solution would be to make the AQ a shared resource by implementing it as a AMQP worker queue, but this would be a much bigger task.
39 changes: 39 additions & 0 deletions applications/acdc/doc/maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## SUP-able functions

| Function | Arguments | Description |
| -------- | --------- | ----------- |
| `agent_detail/2` | `(AccountId,AgentId)` | |
| `agent_login/2` | `(AccountId,AgentId)` | |
| `agent_logout/2` | `(AccountId,AgentId)` | |
| `agent_pause/2` | `(AccountId,AgentId)` | |
| `agent_pause/3` | `(AccountId,AgentId,Timeout)` | |
| `agent_presence_id/2` | `(AccountId,AgentId)` | |
| `agent_queue_login/3` | `(AccountId,AgentId,QueueId)` | |
| `agent_queue_logout/3` | `(AccountId,AgentId,QueueId)` | |
| `agent_resume/2` | `(AccountId,AgentId)` | |
| `agent_summary/2` | `(AccountId,AgentId)` | |
| `agents_detail/0` | | |
| `agents_detail/1` | `(AccountId)` | |
| `agents_summary/0` | | |
| `agents_summary/1` | `(AccountId)` | |
| `current_agents/1` | `(AccountId)` | |
| `current_calls/1` | `(AccountId)` | |
| `current_calls/2` | `(AccountId,Props) | (AccountId,QueueId)` | |
| `current_queues/1` | `(AccountId)` | |
| `current_statuses/1` | `(AccountId)` | |
| `flush_call_stat/1` | `(CallId)` | |
| `logout_agent/2` | `(AccountId,AgentId)` | |
| `logout_agents/1` | `(AccountId)` | |
| `migrate/0` | | |
| `migrate_to_acdc_db/0` | | |
| `queue_detail/2` | `(AccountId,QueueId)` | |
| `queue_restart/2` | `(AccountId,QueueId)` | |
| `queue_summary/2` | `(AccountId,QueueId)` | |
| `queues_detail/0` | | |
| `queues_detail/1` | `(AccountId)` | |
| `queues_restart/1` | `(AccountId)` | |
| `queues_summary/0` | | |
| `queues_summary/1` | `(AccountId)` | |
| `refresh/0` | | |
| `refresh_account/1` | `(Account)` | |
| `register_views/0` | | |
9 changes: 9 additions & 0 deletions applications/acdc/doc/ref/acdc_agent_maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## SUP-able functions

| Function | Arguments | Description |
| -------- | --------- | ----------- |
| `acct_restart/1` | `(AccountId)` | |
| `acct_status/1` | `(AccountId)` | |
| `agent_restart/2` | `(AccountId,AgentId)` | |
| `agent_status/2` | `(AccountId,AgentId)` | |
| `status/0` | | |
40 changes: 40 additions & 0 deletions applications/acdc/doc/ref/maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## SUP-able functions

| Function | Arguments | Description |
| -------- | --------- | ----------- |
| `agent_detail/2` | `(AccountId,AgentId)` | |
| `agent_login/2` | `(AccountId,AgentId)` | |
| `agent_logout/2` | `(AccountId,AgentId)` | |
| `agent_pause/2` | `(AccountId,AgentId)` | |
| `agent_pause/3` | `(AccountId,AgentId,Timeout)` | |
| `agent_presence_id/2` | `(AccountId,AgentId)` | |
| `agent_queue_login/3` | `(AccountId,AgentId,QueueId)` | |
| `agent_queue_logout/3` | `(AccountId,AgentId,QueueId)` | |
| `agent_resume/2` | `(AccountId,AgentId)` | |
| `agent_summary/2` | `(AccountId,AgentId)` | |
| `agents_detail/0` | | |
| `agents_detail/1` | `(AccountId)` | |
| `agents_summary/0` | | |
| `agents_summary/1` | `(AccountId)` | |
| `current_agents/1` | `(AccountId)` | |
| `current_calls/1` | `(AccountId)` | |
| `current_calls/2` | `(AccountId,Props) | (AccountId,QueueId)` | |
| `current_queues/1` | `(AccountId)` | |
| `current_statuses/1` | `(AccountId)` | |
| `flush_call_stat/1` | `(CallId)` | |
| `logout_agent/2` | `(AccountId,AgentId)` | |
| `logout_agents/1` | `(AccountId)` | |
| `migrate/0` | | |
| `migrate_to_acdc_db/0` | | |
| `migrate_to_acdc_db/1` | `(AccountId)` | |
| `queue_detail/2` | `(AccountId,QueueId)` | |
| `queue_restart/2` | `(AccountId,QueueId)` | |
| `queue_summary/2` | `(AccountId,QueueId)` | |
| `queues_detail/0` | | |
| `queues_detail/1` | `(AccountId)` | |
| `queues_restart/1` | `(AccountId)` | |
| `queues_summary/0` | | |
| `queues_summary/1` | `(AccountId)` | |
| `refresh/0` | | |
| `refresh_account/1` | `(Acct)` | |
| `register_views/0` | | |
7 changes: 6 additions & 1 deletion applications/acdc/priv/couchdb/views/acdc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
"language": "javascript",
"views": {
"accounts_listing": {
"map": "function(doc) { if (doc.pvt_type != 'acdc_activation' || doc.pvt_deleted) return; emit(doc.pvt_account_id, null); }"
"map": [
"function(doc) {",
" if (doc.pvt_type != 'acdc_activation' || doc.pvt_deleted) return;",
" emit(doc.pvt_account_id, null);",
"}"
]
}
}
}
21 changes: 18 additions & 3 deletions applications/acdc/priv/couchdb/views/agent_stats.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,28 @@
"language": "javascript",
"views": {
"most_recent_by_agent": {
"map": "function(doc) { if ( doc.pvt_type != 'status_stat' ) return; emit([doc.agent_id, doc.timestamp], null); }"
"map": [
"function(doc) {",
" if (doc.pvt_type != 'status_stat') return;",
" emit([doc.agent_id, doc.timestamp], null);",
"}"
]
},
"most_recent_by_timestamp": {
"map": "function(doc) { if ( doc.pvt_type != 'status_stat' ) return; emit([doc.timestamp, doc.agent_id], null); }"
"map": [
"function(doc) {",
" if (doc.pvt_type != 'status_stat') return;",
" emit([doc.timestamp, doc.agent_id], null);",
"}"
]
},
"status_log": {
"map": "function(doc) { if ( doc.pvt_type != 'status_stat' ) return; emit([doc.agent_id, doc.timestamp], doc.status); }"
"map": [
"function(doc) {",
" if (doc.pvt_type != 'status_stat') return;",
" emit([doc.agent_id, doc.timestamp], doc.status);",
"}"
]
}
}
}
16 changes: 15 additions & 1 deletion applications/acdc/priv/couchdb/views/agents.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@
"view_map": [
{
"classification": "account"
},
{
"database": "acdc"
}
]
},
"language": "javascript",
"views": {
"crossbar_listing": {
"map": "function(doc) { if (doc.pvt_type !== 'user' || typeof doc.queues !== 'object' || doc.pvt_deleted) return; emit(doc._id, {'first_name': doc.first_name, 'last_name': doc.last_name, 'queues':doc.queues}); }"
"map": [
"function(doc) {",
" if (doc.pvt_type !== 'user' || typeof doc.queues !== 'object' || doc.pvt_deleted) return;",
" emit(doc._id, {",
" 'first_name': doc.first_name,",
" 'last_name': doc.last_name,",
" 'queues': doc.queues,",
" 'agent_priority': doc.acdc_agent_priority || 0,",
" 'skills': doc.acdc_skills || []",
" });",
"}"
]
}
}
}
Loading

0 comments on commit fd9354a

Please sign in to comment.