diff --git a/CHANGES b/CHANGES index 7352c64d321f79b8d9082101850d55812f8b8871..9b4f755d6403aaf22a56cf4d0482381de25dac47 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,17 @@ --- Functionality changes from Asterisk 1.6.0 to Asterisk 1.6.1 ------------- ------------------------------------------------------------------------------ +Device State Handling +--------------------- + * The event infrastructure in Asterisk got another big update to help support + distributed events. It currently supports distributed device state and + distributed Voicemail MWI (Message Waiting Indication). A new module has + been merged, res_ais, which facilitates communicating events between servers. + It uses the SAForum AIS (Service Availability Forum Application Interface + Specification) CLM (Cluster Management) and EVT (Event) services to maintain + a cluster of Asterisk servers, and to share events between them. For more + information on setting this up, see doc/distributed_devstate.txt. + Dialplan Functions ------------------ * Added a new dialplan function, AST_CONFIG(), which allows you to access @@ -17,9 +28,9 @@ Dialplan Functions * TIMEOUT() has been modified to be accurate down to the millisecond. * ENUM*() functions now include the following new options: - 'u' returns the full URI and does not strip off the URI-scheme. - - 's' triggers ISN specific rewriting - - 'i' looks for branches into an Infrastructure ENUM tree - - 'd' for a direct DNS lookup without any flipping of digits. + - 's' triggers ISN specific rewriting + - 'i' looks for branches into an Infrastructure ENUM tree + - 'd' for a direct DNS lookup without any flipping of digits. * TXCIDNAME() has a new zone-suffix parameter (which defaults to 'e164.arpa') * CHANNEL() now has options for the maximum, minimum, and standard or normal deviation of jitter, rtt, and loss for a call using chan_sip. @@ -116,9 +127,9 @@ CLI Changes which shows which configuration files are in use. * New CLI commands, "pri show version" and "ss7 show version" that will display which version of libpri and libss7 are being used, respectively. - A new API call was added so trunk will now have to be compiled against - a versions of libpri and libss7 that have them or it will not know that - these libraries exist. + A new API call was added so trunk will now have to be compiled against + a versions of libpri and libss7 that have them or it will not know that + these libraries exist. DNS manager changes ------------------- @@ -443,10 +454,10 @@ Voicemail Changes a web interface of some kind). * Added the support for marking messages as "urgent." There are two methods to accomplish this. One is to pass the 'U' option to VoiceMail(). Another way to mark a message as urgent - is to specify "review=yes" in voicemail.conf. Doing this will cause allow the user to mark - the message as urgent after he has recorded a voicemail by following the voice instructions. - When listening to voicemails using VoiceMailMain urgent messages will be presented before other - messages + is to specify "review=yes" in voicemail.conf. Doing this will cause allow the user to mark + the message as urgent after he has recorded a voicemail by following the voice instructions. + When listening to voicemails using VoiceMailMain urgent messages will be presented before other + messages Queue changes ------------- @@ -480,18 +491,18 @@ Queue changes device state reported. * New configuration option: randomperiodicannounce. If a list of periodic announcements is specified by the periodic-announce option, then one will be chosen randomly when it is time - to play a periodic announcment + to play a periodic announcment * New configuration options: announce-position now takes two more values in addition to "yes" and "no." Two new options, "limit" and "more," are allowed. These are tied to another option, - announce-position-limit. By setting announce-position to "limit" callers will only have their - position announced if their position is less than what is specified by announce-position-limit. - If announce-position is set to "more" then callers beyond the position specified by announce-position-limit - will be told that their are more than announce-position-limit callers waiting. + announce-position-limit. By setting announce-position to "limit" callers will only have their + position announced if their position is less than what is specified by announce-position-limit. + If announce-position is set to "more" then callers beyond the position specified by announce-position-limit + will be told that their are more than announce-position-limit callers waiting. * Two new queue log events have been added. An ADDMEMBER event will be logged when a realtime queue member is added and a REMOVEMEMBER event will be logged - when a realtime queue member is removed. Since there is no calling channel associated - with these events, the string "REALTIME" is placed where the channel's unique id - is typically placed. + when a realtime queue member is removed. Since there is no calling channel associated + with these events, the string "REALTIME" is placed where the channel's unique id + is typically placed. MeetMe Changes -------------- @@ -761,7 +772,7 @@ Miscellaneous * iLBC source code no longer included (see UPGRADE.txt for details) * If compiled with DETECT_DEADLOCKS enabled and if you have glibc, then if deadlock is detected, a backtrace of the stack which led to the lock calls - will be output to the CLI. + will be output to the CLI. * If compiled with DEBUG_THREADS enabled and if you have glibc, then issuing the "core show locks" CLI command will give lock information output as well - as a backtrace of the stack which led to the lock calls. + as a backtrace of the stack which led to the lock calls. diff --git a/apps/app_queue.c b/apps/app_queue.c index 9d95797393c8cf59b5865a5e3a8df46c3f01358d..3f68432a4f5ada41703473ff5968fb8338654992 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -6334,8 +6334,10 @@ static int load_module(void) ast_log(LOG_WARNING, "devicestate taskprocessor reference failed - devicestate notifications will not occur\n"); } - if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, device_state_cb, NULL, AST_EVENT_IE_END))) + if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE_CHANGE, device_state_cb, NULL, AST_EVENT_IE_END))) { res = -1; + } + ast_realtime_require_field("queue_members", "paused", RQ_INTEGER1, 1, "uniqueid", RQ_UINTEGER2, 5, NULL); return res ? AST_MODULE_LOAD_DECLINE : 0; diff --git a/configs/ais.conf.sample b/configs/ais.conf.sample new file mode 100644 index 0000000000000000000000000000000000000000..42991a6cac614d023646830d104828d04fdd2935 --- /dev/null +++ b/configs/ais.conf.sample @@ -0,0 +1,76 @@ +; +; Sample configuration file for res_ais +; * SAForum AIS (Application Interface Specification) +; +; More information on the AIS specification is available from the SAForum. +; * http://www.saforum.org/ +; +; A nice open source implementation of AIS is available called openais. Visit +; the openais website for downloads and more information. +; * http://www.openais.org/ +; + +; +; [general] +; The general section is reserved but not currently used. +; + +; +; Event channels are named distributed groups that share events. Each node +; that is the member of the event channel should have an entry in their +; ais.conf file that indicates that they are a member of the event channel. +; Each node's entry for the event channel also indicates which event types +; will be published to other nodes, as well as which event types this node +; will subscribe to from other nodes in the event channel. +; +; The name of the event channel is the name in brackets that begin a section +; in the configuration file. +; [mwi] +; +; To define an event channel, this entry must be in the configuration section: +; type=event_channel +; +; Indicate that a node is capable of publishing events of a certain type by +; using the publish_event directive. +; publish_event=mwi +; +; Indicate that a node is interested in receiving events of a certain type +; from other nodes in the event channel by using the subscribe_event directive. +; subscribe_event=mwi +; +; Supported event types include: mwi, device_state +; + +; +; This example is for a node that can provide MWI state information, but should +; also be listening for MWI state changes from other nodes. Examples of when +; this would be used are when this is both a voicemail server and also has +; phones directly registered to it. +; +; [mwi] +; type=event_channel +; publish_event=mwi +; subscribe_event=mwi +; + +; +; This example would be used for a node that can provide MWI state to other +; nodes, but does not need to know about MWI state changes that happen on +; any other node. This would most likely be a voicemail server where no +; phones are directly registered. +; +; [mwi] +; type=event_channel +; publish_event=mwi +; + +; +; This example would be used for a node that has phones directly registered +; to it, but does not have direct access to voicemail. So, this node wants +; to be informed about MWI state changes on other voicemail server nodes, but +; is not capable of publishing any state changes. +; +; [mwi] +; type=event_channel +; subscribe_event=mwi +; diff --git a/doc/distributed_devstate.txt b/doc/distributed_devstate.txt new file mode 100644 index 0000000000000000000000000000000000000000..2a685159e3c90faab039aa3c71c0d192660eec0b --- /dev/null +++ b/doc/distributed_devstate.txt @@ -0,0 +1,310 @@ +=============================================================================== +=== +=== Distributed Device State +=== +=== Copyright (C) 2007-2008, Digium, Inc. +=== Russell Bryant <russell@digium.com> +=== +=============================================================================== + +------------------------------------------------------------------------------- +--- INTRODUCTION +------------------------------------------------------------------------------- + +Various changes have been made related to "event handling" in Asterisk. +One of the most important things included in these changes is the ability +to share certain events between servers. The two types of events that can +currently be shared between servers are: + + 1) MWI - Message Waiting Indication + - This gives you a high performance option for letting servers in a + cluster be aware of changes in the state of a mailbox. Instead of + having each server have to poll an ODBC database, this lets the server + that actually made the change to the mailbox generate an event which + will get distributed to the other servers that have subscribed to this + information. + + 2) Device State + - This lets servers in a local cluster inform each other about changes in + the state of a device on that particular server. When the state of a + device changes on any server, the overall state of that device across + the cluster will get recalculated. So, any subscriptions to the state + of a device, such as hints in the dialplan or an application like + Queue() which reads device state, will then reflect the state of a + device across a cluster. + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- OpenAIS Installation +------------------------------------------------------------------------------- + +--- Description --- + +The current solution for providing distributed events with Asterisk is done by +using the AIS (Application Interface Specification), which provides an API for +a distributed event service. While this API is standardized, this code has +been developed exclusively against the open source implementation of AIS called +OpenAIS. + +For more information about OpenAIS, visit their web site: + + http://www.openais.org/ + +--- Download --- + +To quickly downlaod OpenAIS, just check it out of svn: + +$ svn co http://svn.osdl.org/openais/trunk openais-trunk + +--- Compile --- + +$ cd openais-trunk +$ make PREFIX=/usr + +--- Install --- + +By default, the current Makefile installs the libraries into /usr/lib/openais/, +which is a little bit inconvenient. So, open up the Makefile, find the lines +that start with "LIBDIR=" to define the lib installation directory, and remove +the trailing "openais" so it just gets installed in /usr/lib/. + +$ sudo make install PREFIX=/usr + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- OpenAIS Configuration +------------------------------------------------------------------------------- + +Basic OpenAIS configuration to get this working is actually pretty easy. When +you install it, it will put some default configuration files into /etc/ais/. +Edit openais.conf ... + +$ ${EDITOR:-vim} /etc/ais/openais.conf + +The only section that you should need to change is the totem - interface +section. + +totem { + ... + interface { + interface { + ringnumber: 0 + bindnetaddr: 10.19.0.0 + mcastaddr: 226.94.1.1 + mcastport: 5405 + } +} + +The default mcastaddr and mcastport is probably fine. But, you need to change +the bindnetaddr to match the network address that the nodes of your cluster +will communicate on. + +The one other thing that you need to do is create a user called "ais". + +$ sudo adduser ais + +See the OpenAIS QUICKSTART file for more information on installing, +configuring, and testing OpenAIS. + +$ cd openais-trunk +$ less QUICKSTART + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- Running OpenAIS +------------------------------------------------------------------------------- + +While testing, I would recommend starting the aisexec application in the +foreground so that you can see debug messages that verify that the nodes have +discovered each other and joined the cluster. + +$ sudo aisexec -f + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- Installing Asterisk +------------------------------------------------------------------------------- + +Install Asterisk as usual. Just make sure that you run the configure script +after OpenAIS gets installed. That way, it will find the AIS header files and +will let you build the res_ais module. Check menuselect to make sure that +res_ais is going to get compiled and installed. + +$ cd asterisk-events +$ ./configure + +$ make menuselect + ---> Resource Modules + +If you have existing configuration on the system being used for testing, just +be sure to install the addition configuration file needed for res_ais. + +$ sudo cp configs/ais.conf.sample /etc/asterisk/ais.conf + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- Configuring Asterisk +------------------------------------------------------------------------------- + +First, ensure that you have a unique "entity ID" set for each server. + +*CLI> core show settings + ... + Entity ID: 01:23:45:67:89:ab + +The code will attempt to generate a unique entity ID for you by reading +MAC addresses off of a network interface. However, you can also set it +manually in the [options] section of asterisk.conf. + +$ sudo ${EDITOR:-vim} /etc/asterisk/asterisk.conf + +[options] +... +entity_id=01:23:45:67:89:ab + + +Edit the Asterisk ais.conf to enable distributed events. For example, if you +would like to enable distributed device state, you should add the following +section to the file: + +$ sudo ${EDITOR:-vim} /etc/asterisk/ais.conf + +[device_state] +type=event_channel +publish_event=device_state +subscribe_event=device_state + +For more information on the contents and available options in this configuration +file, please see the sample configuration file: + +$ cd asterisk-events +$ less configs/ais.conf.sample + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- Basic Testing of Asterisk with OpenAIS +------------------------------------------------------------------------------- + +If you have OpenAIS successfully installed and running, as well as Asterisk +with OpenAIS support successfully installed, configured, and running, then you +are ready to test out some of the AIS functionality in Asterisk. + +The first thing to test is to verify that all of the nodes that you think should +be in your cluster are actually there. There is an Asterisk CLI command which +will list the current cluster members using the AIS Cluster Membership Service +(CLM). + +*CLI> ais clm show members + +============================================================= +=== Cluster Members ========================================= +============================================================= +=== +=== --------------------------------------------------------- +=== Node Name: 10.19.2.255 +=== ==> ID: 0xa1302ff +=== ==> Address: 10.19.2.255 +=== ==> Member: Yes +=== --------------------------------------------------------- +=== +=== --------------------------------------------------------- +=== Node Name: 10.19.6.187 +=== ==> ID: 0xa1306bb +=== ==> Address: 10.19.6.187 +=== ==> Member: Yes +=== --------------------------------------------------------- +=== +============================================================= + + +The next thing to do is to verify that you have successfully configured some +event channels in the Asterisk ais.conf file. This command is related to the +event service (EVT), so like the previous command, uses the syntax: +"ais <service name> <command>". + +*CLI> ais evt show event channels + +============================================================= +=== Event Channels ========================================== +============================================================= +=== +=== --------------------------------------------------------- +=== Event Channel Name: mwi +=== ==> Publishing Event Type: mwi +=== ==> Subscribing to Event Type: mwi +=== --------------------------------------------------------- +=== +=== --------------------------------------------------------- +=== Event Channel Name: device_state +=== ==> Publishing Event Type: device_state +=== ==> Subscribing to Event Type: device_state +=== --------------------------------------------------------- +=== +============================================================= + +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +--- Testing Distributed Device State +------------------------------------------------------------------------------- + +The easiest way to test distributed device state is to use the DEVICE_STATE() +diaplan function. For example, you could have the following piece of dialplan +on every server: + +[devstate_test] + +exten => 1234,hint,Custom:mystate + +exten => set_inuse,1,Set(DEVICE_STATE(Custom:mystate)=INUSE) +exten => set_not_inuse,1,Set(DEVICE_STATE(Custom:mystate)=NOT_INUSE) + +exten => check,1,NoOp(Custom:mystate is ${DEVICE_STATE(Custom:mystate)}) + + +Now, you can test that the cluster-wide state of "Custom:mystate" is what +you would expect after going to the CLI of each server and adjusting the state. + +server1*CLI> console dial set_inuse@devstate_test + ... + +server2*CLI> console dial check@devstate_test + -- Executing [check@devstate_test:1] NoOp("OSS/dsp", "Custom:mystate is INUSE") in new stack + +Various combinations of setting and checking the state on different servers can +be used to verify that it works as expected. Also, you can see the status of +the hint on each server, as well, to see how extension state would reflect the +state change with distributed device state: + +server2*CLI> core show hints + -= Registered Asterisk Dial Plan Hints =- + 1234@devstate_test : Custom:mystate State:InUse Watchers 0 + + +One other helpful thing here during testing and debugging is to enable debug +logging. To do so, enable debug on the console in /etc/asterisk/logger.conf. +Also, enable debug at the Asterisk CLI. + +*CLI> core set debug 1 + +When you have this debug enabled, you will see output during the processing of +every device state change. The important thing to look for is where the known +state of the device for each server is added together to determine the overall +state. + +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +--- Question, Comments, and Bug Reports +------------------------------------------------------------------------------- + +For now, please direct all feedback to Russell Bryant <russell@digium.com>. + +------------------------------------------------------------------------------- diff --git a/main/devicestate.c b/main/devicestate.c index 85ac6492e016f797a82ce0efb6cb94c837c088ef..8c20d1703f5dd324a680410735b07cbbe13e361b 100644 --- a/main/devicestate.c +++ b/main/devicestate.c @@ -1,9 +1,10 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 1999 - 2007, Digium, Inc. + * Copyright (C) 1999 - 2008, Digium, Inc. * * Mark Spencer <markster@digium.com> + * Russell Bryant <russell@digium.com> * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -20,13 +21,17 @@ * * \brief Device state management * - * * \author Mark Spencer <markster@digium.com> + * \author Russell Bryant <russell@digium.com> * * \arg \ref AstExtState */ /*! \page AstExtState Extension and device states in Asterisk + * + * (Note that these descriptions of device states and extension + * states have not been updated to the way things work + * in Asterisk 1.6.) * * Asterisk has an internal system that reports states * for an extension. By using the dialplan priority -1, @@ -171,6 +176,23 @@ enum devstate_cache { CACHE_OFF, }; +struct devstate_change { + AST_LIST_ENTRY(devstate_change) entry; + uint32_t state; + struct ast_eid eid; + char device[1]; +}; + +struct { + pthread_t thread; + struct ast_event_sub *event_sub; + ast_cond_t cond; + ast_mutex_t lock; + AST_LIST_HEAD_NOLOCK(, devstate_change) devstate_change_q; +} devstate_collector = { + .thread = AST_PTHREADT_NULL, +}; + /* Forward declarations */ static int getproviderstate(const char *provider, const char *address); @@ -271,7 +293,7 @@ static enum ast_device_state devstate_cached(const char *device) enum ast_device_state res = AST_DEVICE_UNKNOWN; struct ast_event *event; - event = ast_event_get_cached(AST_EVENT_DEVICE_STATE, + event = ast_event_get_cached(AST_EVENT_DEVICE_STATE_CHANGE, AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device, AST_EVENT_IE_END); @@ -383,7 +405,6 @@ static int getproviderstate(const char *provider, const char *address) struct devstate_prov *devprov; int res = AST_DEVICE_INVALID; - AST_RWLIST_RDLOCK(&devstate_provs); AST_RWLIST_TRAVERSE(&devstate_provs, devprov, list) { ast_debug(5, "Checking provider %s with %s\n", devprov->label, provider); @@ -394,6 +415,7 @@ static int getproviderstate(const char *provider, const char *address) } } AST_RWLIST_UNLOCK(&devstate_provs); + return res; } @@ -401,7 +423,9 @@ static void devstate_event(const char *device, enum ast_device_state state, enum { struct ast_event *event; - if (!(event = ast_event_new(AST_EVENT_DEVICE_STATE, + ast_debug(1, "device '%s' state '%d'\n", device, state); + + if (!(event = ast_event_new(AST_EVENT_DEVICE_STATE_CHANGE, AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device, AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, state, AST_EVENT_IE_END))) { @@ -413,6 +437,7 @@ static void devstate_event(const char *device, enum ast_device_state state, enum * device name if it exists. */ ast_event_queue_and_cache(event, AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, + AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, sizeof(struct ast_eid), AST_EVENT_IE_END); } else { ast_event_queue(event); @@ -540,9 +565,189 @@ static void *do_devstate_changes(void *data) return NULL; } +static void destroy_devstate_change(struct devstate_change *sc) +{ + ast_free(sc); +} + +#define MAX_SERVERS 64 +struct change_collection { + struct devstate_change states[MAX_SERVERS]; + size_t num_states; +}; + +static void devstate_cache_cb(const struct ast_event *event, void *data) +{ + struct change_collection *collection = data; + int i; + const struct ast_eid *eid; + + if (collection->num_states == ARRAY_LEN(collection->states)) { + ast_log(LOG_ERROR, "More per-server state values than we have room for (MAX_SERVERS is %d)\n", + MAX_SERVERS); + return; + } + + if (!(eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID))) { + ast_log(LOG_ERROR, "Device state change event with no EID\n"); + return; + } + + i = collection->num_states; + + collection->states[i].state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE); + collection->states[i].eid = *eid; + + collection->num_states++; +} + +static void process_collection(const char *device, struct change_collection *collection) +{ + int i; + struct ast_devstate_aggregate agg; + enum ast_device_state state; + struct ast_event *event; + + ast_devstate_aggregate_init(&agg); + + for (i = 0; i < collection->num_states; i++) { + ast_debug(1, "Adding per-server state of '%s' for '%s'\n", + devstate2str(collection->states[i].state), device); + ast_devstate_aggregate_add(&agg, collection->states[i].state); + } + + state = ast_devstate_aggregate_result(&agg); + + ast_debug(1, "Aggregate devstate result is %d\n", state); + + event = ast_event_get_cached(AST_EVENT_DEVICE_STATE, + AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device, + AST_EVENT_IE_END); + + if (event) { + enum ast_device_state old_state; + + old_state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE); + + ast_event_destroy(event); + + if (state == old_state) { + /* No change since last reported device state */ + ast_debug(1, "Aggregate state for device '%s' has not changed from '%s'\n", + device, devstate2str(state)); + return; + } + } + + ast_debug(1, "Aggregate state for device '%s' has changed to '%s'\n", + device, devstate2str(state)); + + event = ast_event_new(AST_EVENT_DEVICE_STATE, + AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device, + AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, state, + AST_EVENT_IE_END); + + if (!event) + return; + + ast_event_queue_and_cache(event, + AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, + AST_EVENT_IE_END); +} + +static void handle_devstate_change(struct devstate_change *sc) +{ + struct ast_event_sub *tmp_sub; + struct change_collection collection = { + .num_states = 0, + }; + + ast_debug(1, "Processing device state change for '%s'\n", sc->device); + + if (!(tmp_sub = ast_event_subscribe_new(AST_EVENT_DEVICE_STATE_CHANGE, devstate_cache_cb, &collection))) { + ast_log(LOG_ERROR, "Failed to create subscription\n"); + return; + } + + if (ast_event_sub_append_ie_str(tmp_sub, AST_EVENT_IE_DEVICE, sc->device)) { + ast_log(LOG_ERROR, "Failed to append device IE\n"); + ast_event_sub_destroy(tmp_sub); + return; + } + + /* Populate the collection of device states from the cache */ + ast_event_dump_cache(tmp_sub); + + process_collection(sc->device, &collection); + + ast_event_sub_destroy(tmp_sub); +} + +static void *run_devstate_collector(void *data) +{ + for (;;) { + struct devstate_change *sc; + + ast_mutex_lock(&devstate_collector.lock); + while (!(sc = AST_LIST_REMOVE_HEAD(&devstate_collector.devstate_change_q, entry))) + ast_cond_wait(&devstate_collector.cond, &devstate_collector.lock); + ast_mutex_unlock(&devstate_collector.lock); + + handle_devstate_change(sc); + + destroy_devstate_change(sc); + } + + return NULL; +} + +static void devstate_change_collector_cb(const struct ast_event *event, void *data) +{ + struct devstate_change *sc; + const char *device; + const struct ast_eid *eid; + uint32_t state; + + device = ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE); + eid = ast_event_get_ie_raw(event, AST_EVENT_IE_EID); + state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE); + + if (ast_strlen_zero(device) || !eid) { + ast_log(LOG_ERROR, "Invalid device state change event received\n"); + return; + } + + if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(device)))) + return; + + strcpy(sc->device, device); + sc->eid = *eid; + sc->state = state; + + ast_mutex_lock(&devstate_collector.lock); + AST_LIST_INSERT_TAIL(&devstate_collector.devstate_change_q, sc, entry); + ast_cond_signal(&devstate_collector.cond); + ast_mutex_unlock(&devstate_collector.lock); +} + /*! \brief Initialize the device state engine in separate thread */ int ast_device_state_engine_init(void) { + devstate_collector.event_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE_CHANGE, + devstate_change_collector_cb, NULL, AST_EVENT_IE_END); + + if (!devstate_collector.event_sub) { + ast_log(LOG_ERROR, "Failed to create subscription for the device state change collector\n"); + return -1; + } + + ast_mutex_init(&devstate_collector.lock); + ast_cond_init(&devstate_collector.cond, NULL); + if (ast_pthread_create_background(&devstate_collector.thread, NULL, run_devstate_collector, NULL) < 0) { + ast_log(LOG_ERROR, "Unable to start device state collector thread.\n"); + return -1; + } + ast_cond_init(&change_pending, NULL); if (ast_pthread_create_background(&change_thread, NULL, do_devstate_changes, NULL) < 0) { ast_log(LOG_ERROR, "Unable to start device state change thread.\n"); diff --git a/main/pbx.c b/main/pbx.c index fcc3fb3f060353f08e1041c7a3a72c50ae0f21bf..6bb8a14f643444af67c790c1952442f56a31bbff 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -8070,7 +8070,7 @@ int load_pbx(void) /* Register manager application */ ast_manager_register2("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan, "List dialplan", mandescr_show_dialplan); - if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, device_state_cb, NULL, + if (!(device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE_CHANGE, device_state_cb, NULL, AST_EVENT_IE_END))) { return -1; } diff --git a/res/Makefile b/res/Makefile index 92f0f336eb2e3c8176d710b6fd2253326d4daa21..a7ba0833fd876ca028cb82ee03a764cce8396469 100644 --- a/res/Makefile +++ b/res/Makefile @@ -35,6 +35,8 @@ ael/ael_lex.o: ASTCFLAGS+=-I. -Iael -Wno-unused ael/ael.tab.o: ael/ael.tab.c ael/ael.tab.h ../include/asterisk/ael_structs.h ael/ael.tab.o: ASTCFLAGS+=-I. -Iael -DYYENABLE_NLS=0 +$(if $(filter res_ais,$(EMBEDDED_MODS)),modules.link,res_ais.so): ais/clm.o ais/evt.o + $(if $(filter res_snmp,$(EMBEDDED_MODS)),modules.link,res_snmp.so): snmp/agent.o $(if $(filter res_ael_share,$(EMBEDDED_MODS)),modules.link,res_ael_share.so): ael/ael_lex.o ael/ael.tab.o ael/pval.o @@ -49,5 +51,4 @@ ael/ael.tab.c ael/ael.tab.h: ael/pval.o: ael/pval.c clean:: - rm -f snmp/*.o - rm -f ael/*.o + rm -f snmp/*.o ael/*.o ais/*.o diff --git a/res/ais/ais.h b/res/ais/ais.h new file mode 100644 index 0000000000000000000000000000000000000000..2c4c18a87b5ba0025df9592340b33a084d4029fe --- /dev/null +++ b/res/ais/ais.h @@ -0,0 +1,48 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + */ + +#ifndef AST_AIS_H +#define AST_AIS_H + +#include <openais/saAis.h> +#include <openais/saClm.h> +#include <openais/saEvt.h> + +extern SaVersionT ais_version; + +extern SaClmHandleT clm_handle; +extern SaEvtHandleT evt_handle; + +int ast_ais_clm_load_module(void); +int ast_ais_clm_unload_module(void); + +int ast_ais_evt_load_module(void); +int ast_ais_evt_unload_module(void); + +const char *ais_err2str(SaAisErrorT error); + +#endif /* AST_AIS_H */ diff --git a/res/ais/amf.c b/res/ais/amf.c new file mode 100644 index 0000000000000000000000000000000000000000..fec9af6e0008624af1dcb87a5dc5e62f961e90f1 --- /dev/null +++ b/res/ais/amf.c @@ -0,0 +1,89 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + * + * This file contains the code specific to the use of the AMF (Application + * Management Framework). + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ais.h" + +#include "asterisk/module.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" + +SaAmfHandleT amf_handle; + +static const SaAmfCallbacksT amf_callbacks = { + .saAmfHealthcheckCallback = NULL, + .saAmfComponentTerminateCallback = NULL, + .saAmfCSISetCallback = NULL, + .saAmfProtectionGroupTrackCallback = NULL, +#if 0 + /*! XXX \todo These appear to be define in the B.02.01 spec, but this won't + * compile with them in there. Look into it some more ... */ + .saAmfProxiedComponentInstantiateCallback = NULL, + .saAmfProxiedComponentCleanupCallback = NULL, +#endif +}; + +int ast_ais_amf_load_module(void) +{ + SaAisErrorT ais_res; + + ais_res = saAmfInitialize(&amf_handle, &amf_callbacks, &ais_version); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Could not initialize AMF: %s\n", + ais_err2str(ais_res)); + return -1; + } + + return 0; +} + +int ast_ais_amf_unload_module(void) +{ + SaAisErrorT ais_res; + + ais_res = saAmfFinalize(amf_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Problem stopping AMF: %s\n", + ais_err2str(ais_res)); + return -1; + } + + return 0; +} diff --git a/res/ais/ckpt.c b/res/ais/ckpt.c new file mode 100644 index 0000000000000000000000000000000000000000..bdf4b312c4f16af5be407e0020221001ffac6071 --- /dev/null +++ b/res/ais/ckpt.c @@ -0,0 +1,78 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + * + * This file contains the code specific to the use of the CKPT (Checkpoint) + * service. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ais.h" + +#include "asterisk/module.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" + +SaCkptHandleT ckpt_handle; + +static const SaCkptCallbacksT ckpt_callbacks; + +int ast_ais_ckpt_load_module(void) +{ + SaAisErrorT ais_res; + + ais_res = saCkptInitialize(&ckpt_handle, &ckpt_callbacks, &ais_version); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Could not initialize CKPT service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + return 0; +} + +int ast_ais_ckpt_unload_module(void) +{ + SaAisErrorT ais_res; + + ais_res = saCkptFinalize(amf_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Problem stopping CKPT service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + return 0; +} diff --git a/res/ais/clm.c b/res/ais/clm.c new file mode 100644 index 0000000000000000000000000000000000000000..5d7a356beb72033076a5d33735042883ee43a474 --- /dev/null +++ b/res/ais/clm.c @@ -0,0 +1,165 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + * + * This file contains the code specific to the use of the CLM + * (Cluster Membership) Service. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ais.h" + +#include "asterisk/module.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" + +SaClmHandleT clm_handle; + +static void clm_node_get_cb(SaInvocationT invocation, + const SaClmClusterNodeT *cluster_node, SaAisErrorT error); +static void clm_track_cb(const SaClmClusterNotificationBufferT *notif_buffer, + SaUint32T num_members, SaAisErrorT error); + +static const SaClmCallbacksT clm_callbacks = { + .saClmClusterNodeGetCallback = clm_node_get_cb, + .saClmClusterTrackCallback = clm_track_cb, +}; + +static void clm_node_get_cb(SaInvocationT invocation, + const SaClmClusterNodeT *cluster_node, SaAisErrorT error) +{ + +} + +static void clm_track_cb(const SaClmClusterNotificationBufferT *notif_buffer, + SaUint32T num_members, SaAisErrorT error) +{ + +} + +static char *ais_clm_show_members(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + int i; + SaClmClusterNotificationBufferT buf; + SaClmClusterNotificationT notif[64]; + SaAisErrorT ais_res; + + switch (cmd) { + case CLI_INIT: + e->command = "ais clm show members"; + e->usage = + "Usage: ais clm show members\n" + " List members of the cluster using the CLM (Cluster Membership) service.\n"; + return NULL; + + case CLI_GENERATE: + return NULL; /* no completion */ + } + + if (a->argc != e->args) + return CLI_SHOWUSAGE; + + buf.notification = notif; + buf.numberOfItems = ARRAY_LEN(notif); + + ais_res = saClmClusterTrack(clm_handle, SA_TRACK_CURRENT, &buf); + if (ais_res != SA_AIS_OK) { + ast_cli(a->fd, "Error retrieving current cluster members.\n"); + return CLI_FAILURE; + } + + ast_cli(a->fd, "\n" + "=============================================================\n" + "=== Cluster Members =========================================\n" + "=============================================================\n" + "===\n"); + + for (i = 0; i < buf.numberOfItems; i++) { + SaClmClusterNodeT *node = &buf.notification[i].clusterNode; + + ast_cli(a->fd, "=== ---------------------------------------------------------\n" + "=== Node Name: %s\n" + "=== ==> ID: 0x%x\n" + "=== ==> Address: %s\n" + "=== ==> Member: %s\n", + (char *) node->nodeName.value, (int) node->nodeId, + (char *) node->nodeAddress.value, + node->member ? "Yes" : "No"); + + ast_cli(a->fd, "=== ---------------------------------------------------------\n" + "===\n"); + } + + ast_cli(a->fd, "=============================================================\n" + "\n"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry ais_cli[] = { + AST_CLI_DEFINE(ais_clm_show_members, "List current members of the cluster"), +}; + +int ast_ais_clm_load_module(void) +{ + SaAisErrorT ais_res; + + ais_res = saClmInitialize(&clm_handle, &clm_callbacks, &ais_version); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Could not initialize cluster membership service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + ast_cli_register_multiple(ais_cli, ARRAY_LEN(ais_cli)); + + return 0; +} + +int ast_ais_clm_unload_module(void) +{ + SaAisErrorT ais_res; + + ast_cli_unregister_multiple(ais_cli, ARRAY_LEN(ais_cli)); + + ais_res = saClmFinalize(clm_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Problem stopping cluster membership service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + return 0; +} diff --git a/res/ais/evt.c b/res/ais/evt.c new file mode 100644 index 0000000000000000000000000000000000000000..0057f0481a1e7262f5fa27c071175986c927288c --- /dev/null +++ b/res/ais/evt.c @@ -0,0 +1,588 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + * + * This file contains the code specific to the use of the EVT + * (Event) Service. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ais.h" + +#include "asterisk/module.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" +#include "asterisk/event.h" +#include "asterisk/config.h" +#include "asterisk/linkedlists.h" + +#ifndef AST_MODULE +/* XXX HACK */ +#define AST_MODULE "res_ais" +#endif + +SaEvtHandleT evt_handle; + +void evt_channel_open_cb(SaInvocationT invocation, SaEvtChannelHandleT channel_handle, + SaAisErrorT error); +void evt_event_deliver_cb(SaEvtSubscriptionIdT subscription_id, + const SaEvtEventHandleT event_handle, const SaSizeT event_datalen); + +static const SaEvtCallbacksT evt_callbacks = { + .saEvtChannelOpenCallback = evt_channel_open_cb, + .saEvtEventDeliverCallback = evt_event_deliver_cb, +}; + +static const struct { + const char *str; + enum ast_event_type type; +} supported_event_types[] = { + { "mwi", AST_EVENT_MWI }, + { "device_state", AST_EVENT_DEVICE_STATE_CHANGE }, +}; + +/*! Used to provide unique id's to egress subscriptions */ +static int unique_id; + +struct subscribe_event { + AST_LIST_ENTRY(subscribe_event) entry; + /*! This is a unique identifier to identify this subscription in the event + * channel through the different API calls, subscribe, unsubscribe, and + * the event deliver callback. */ + SaEvtSubscriptionIdT id; + enum ast_event_type type; +}; + +struct publish_event { + AST_LIST_ENTRY(publish_event) entry; + /*! We subscribe to events internally so that we can publish them + * on this event channel. */ + struct ast_event_sub *sub; + enum ast_event_type type; +}; + +struct event_channel { + AST_RWLIST_ENTRY(event_channel) entry; + AST_LIST_HEAD_NOLOCK(, subscribe_event) subscribe_events; + AST_LIST_HEAD_NOLOCK(, publish_event) publish_events; + SaEvtChannelHandleT handle; + char name[1]; +}; + +static AST_RWLIST_HEAD_STATIC(event_channels, event_channel); + +void evt_channel_open_cb(SaInvocationT invocation, SaEvtChannelHandleT channel_handle, + SaAisErrorT error) +{ + +} + +static void queue_event(struct ast_event *ast_event) +{ + enum ast_event_type type; + + /*! + * \todo This hack macks me sad. I need to come up with a better way to + * figure out whether an event should be cached or not, and what + * parameters to cache on. + * + * As long as the types of events that are supported is limited, + * this isn't *terrible*, I guess. Perhaps we should just define + * caching rules in the core, and make them configurable, and not + * have it be the job of the event publishers. + */ + + type = ast_event_get_type(ast_event); + + if (type == AST_EVENT_MWI) { + ast_event_queue_and_cache(ast_event, + AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, + AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, + AST_EVENT_IE_END); + } else if (type == AST_EVENT_DEVICE_STATE_CHANGE) { + ast_event_queue_and_cache(ast_event, + AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, + AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, sizeof(struct ast_eid), + AST_EVENT_IE_END); + } else { + ast_event_queue(ast_event); + } +} + +void evt_event_deliver_cb(SaEvtSubscriptionIdT sub_id, + const SaEvtEventHandleT event_handle, const SaSizeT event_datalen) +{ + /* It is important to note that this works because we *know* that this + * function will only be called by a single thread, the dispatch_thread. + * If this module gets changed such that this is no longer the case, this + * should get changed to a thread-local buffer, instead. */ + static unsigned char buf[4096]; + struct ast_event *event_dup, *event = (void *) buf; + SaAisErrorT ais_res; + SaSizeT len = sizeof(buf); + + if (event_datalen > len) { + ast_log(LOG_ERROR, "Event received with size %u, which is too big\n" + "for the allocated size %u. Change the code to increase the size.\n", + (unsigned int) event_datalen, (unsigned int) len); + return; + } + + ais_res = saEvtEventDataGet(event_handle, event, &len); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error retrieving event payload: %s\n", + ais_err2str(ais_res)); + return; + } + + if (!ast_eid_cmp(&g_eid, ast_event_get_ie_raw(event, AST_EVENT_IE_EID))) { + /* Don't feed events back in that originated locally. */ + return; + } + + if (!(event_dup = ast_malloc(len))) + return; + + memcpy(event_dup, event, len); + + queue_event(event_dup); +} + +static const char *type_to_filter_str(enum ast_event_type type) +{ + const char *filter_str = NULL; + int i; + + for (i = 0; i < ARRAY_LEN(supported_event_types); i++) { + if (supported_event_types[i].type == type) { + filter_str = supported_event_types[i].str; + break; + } + } + + return filter_str; +} + +static void ast_event_cb(const struct ast_event *ast_event, void *data) +{ + SaEvtEventHandleT event_handle; + SaAisErrorT ais_res; + struct event_channel *event_channel = data; + SaClmClusterNodeT local_node; + SaEvtEventPatternArrayT pattern_array; + SaEvtEventPatternT pattern; + SaSizeT len; + const char *filter_str; + SaEvtEventIdT event_id; + + ast_log(LOG_DEBUG, "Got an event to forward\n"); + + if (ast_eid_cmp(&g_eid, ast_event_get_ie_raw(ast_event, AST_EVENT_IE_EID))) { + /* If the event didn't originate from this server, don't send it back out. */ + ast_log(LOG_DEBUG, "Returning here\n"); + return; + } + + ais_res = saEvtEventAllocate(event_channel->handle, &event_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error allocating event: %s\n", ais_err2str(ais_res)); + ast_log(LOG_DEBUG, "Returning here\n"); + return; + } + + ais_res = saClmClusterNodeGet(clm_handle, SA_CLM_LOCAL_NODE_ID, + SA_TIME_ONE_SECOND, &local_node); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error getting local node name: %s\n", ais_err2str(ais_res)); + goto return_event_free; + } + + filter_str = type_to_filter_str(ast_event_get_type(ast_event)); + len = strlen(filter_str) + 1; + pattern.pattern = (SaUint8T *) filter_str; + pattern.patternSize = len; + pattern.allocatedSize = len; + + pattern_array.allocatedNumber = 1; + pattern_array.patternsNumber = 1; + pattern_array.patterns = &pattern; + + /*! + * /todo Make retention time configurable + * /todo Make event priorities configurable + */ + ais_res = saEvtEventAttributesSet(event_handle, &pattern_array, + SA_EVT_LOWEST_PRIORITY, SA_TIME_ONE_MINUTE, &local_node.nodeName); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error setting event attributes: %s\n", ais_err2str(ais_res)); + goto return_event_free; + } + + ais_res = saEvtEventPublish(event_handle, + ast_event, ast_event_get_size(ast_event), &event_id); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error publishing event: %s\n", ais_err2str(ais_res)); + goto return_event_free; + } + +return_event_free: + ais_res = saEvtEventFree(event_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error freeing allocated event: %s\n", ais_err2str(ais_res)); + } + ast_log(LOG_DEBUG, "Returning here (event_free)\n"); +} + +static char *ais_evt_show_event_channels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct event_channel *event_channel; + + switch (cmd) { + case CLI_INIT: + e->command = "ais evt show event channels"; + e->usage = + "Usage: ais evt show event channels\n" + " List configured event channels for the (EVT) Eventing service.\n"; + return NULL; + + case CLI_GENERATE: + return NULL; /* no completion */ + } + + if (a->argc != e->args) + return CLI_SHOWUSAGE; + + ast_cli(a->fd, "\n" + "=============================================================\n" + "=== Event Channels ==========================================\n" + "=============================================================\n" + "===\n"); + + AST_RWLIST_RDLOCK(&event_channels); + AST_RWLIST_TRAVERSE(&event_channels, event_channel, entry) { + struct publish_event *publish_event; + struct subscribe_event *subscribe_event; + + ast_cli(a->fd, "=== ---------------------------------------------------------\n" + "=== Event Channel Name: %s\n", event_channel->name); + + AST_LIST_TRAVERSE(&event_channel->publish_events, publish_event, entry) { + ast_cli(a->fd, "=== ==> Publishing Event Type: %s\n", + type_to_filter_str(publish_event->type)); + } + + AST_LIST_TRAVERSE(&event_channel->subscribe_events, subscribe_event, entry) { + ast_cli(a->fd, "=== ==> Subscribing to Event Type: %s\n", + type_to_filter_str(subscribe_event->type)); + } + + ast_cli(a->fd, "=== ---------------------------------------------------------\n" + "===\n"); + } + AST_RWLIST_UNLOCK(&event_channels); + + ast_cli(a->fd, "=============================================================\n" + "\n"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry ais_cli[] = { + AST_CLI_DEFINE(ais_evt_show_event_channels, "Show configured event channels"), +}; + +static void add_publish_event(struct event_channel *event_channel, const char *event_type) +{ + int i; + enum ast_event_type type = -1; + struct publish_event *publish_event; + + for (i = 0; i < ARRAY_LEN(supported_event_types); i++) { + if (!strcasecmp(event_type, supported_event_types[i].str)) { + type = supported_event_types[i].type; + break; + } + } + + if (type == -1) { + ast_log(LOG_WARNING, "publish_event option given with invalid value '%s'\n", event_type); + return; + } + + if (!(publish_event = ast_calloc(1, sizeof(*publish_event)))) + return; + + publish_event->type = type; + ast_log(LOG_DEBUG, "Subscribing to event type %d\n", type); + publish_event->sub = ast_event_subscribe(type, ast_event_cb, event_channel, + AST_EVENT_IE_END); + ast_event_dump_cache(publish_event->sub); + + AST_LIST_INSERT_TAIL(&event_channel->publish_events, publish_event, entry); +} + +static SaAisErrorT set_egress_subscription(struct event_channel *event_channel, + struct subscribe_event *subscribe_event) +{ + SaAisErrorT ais_res; + SaEvtEventFilterArrayT filter_array; + SaEvtEventFilterT filter; + const char *filter_str = NULL; + SaSizeT len; + + /* We know it's going to be valid. It was checked earlier. */ + filter_str = type_to_filter_str(subscribe_event->type); + + filter.filterType = SA_EVT_EXACT_FILTER; + len = strlen(filter_str) + 1; + filter.filter.allocatedSize = len; + filter.filter.patternSize = len; + filter.filter.pattern = (SaUint8T *) filter_str; + + filter_array.filtersNumber = 1; + filter_array.filters = &filter; + + ais_res = saEvtEventSubscribe(event_channel->handle, &filter_array, + subscribe_event->id); + + return ais_res; +} + +static void add_subscribe_event(struct event_channel *event_channel, const char *event_type) +{ + int i; + enum ast_event_type type = -1; + struct subscribe_event *subscribe_event; + SaAisErrorT ais_res; + + for (i = 0; i < ARRAY_LEN(supported_event_types); i++) { + if (!strcasecmp(event_type, supported_event_types[i].str)) { + type = supported_event_types[i].type; + break; + } + } + + if (type == -1) { + ast_log(LOG_WARNING, "subscribe_event option given with invalid value '%s'\n", event_type); + return; + } + + if (!(subscribe_event = ast_calloc(1, sizeof(*subscribe_event)))) + return; + + subscribe_event->type = type; + subscribe_event->id = ast_atomic_fetchadd_int(&unique_id, +1); + + ais_res = set_egress_subscription(event_channel, subscribe_event); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error setting up egress subscription: %s\n", + ais_err2str(ais_res)); + free(subscribe_event); + return; + } + + AST_LIST_INSERT_TAIL(&event_channel->subscribe_events, subscribe_event, entry); +} + +static void build_event_channel(struct ast_config *cfg, const char *cat) +{ + struct ast_variable *var; + struct event_channel *event_channel; + SaAisErrorT ais_res; + SaNameT sa_name = { 0, }; + + AST_RWLIST_WRLOCK(&event_channels); + AST_RWLIST_TRAVERSE(&event_channels, event_channel, entry) { + if (!strcasecmp(event_channel->name, cat)) + break; + } + AST_RWLIST_UNLOCK(&event_channels); + if (event_channel) { + ast_log(LOG_WARNING, "Event channel '%s' was specified twice in " + "configuration. Second instance ignored.\n", cat); + return; + } + + if (!(event_channel = ast_calloc(1, sizeof(*event_channel) + strlen(cat)))) + return; + + strcpy(event_channel->name, cat); + ast_copy_string((char *) sa_name.value, cat, sizeof(sa_name.value)); + sa_name.length = strlen((char *) sa_name.value); + ais_res = saEvtChannelOpen(evt_handle, &sa_name, + SA_EVT_CHANNEL_PUBLISHER | SA_EVT_CHANNEL_SUBSCRIBER | SA_EVT_CHANNEL_CREATE, + SA_TIME_MAX, &event_channel->handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error opening event channel: %s\n", ais_err2str(ais_res)); + free(event_channel); + return; + } + + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "type")) { + continue; + } else if (!strcasecmp(var->name, "publish_event")) { + add_publish_event(event_channel, var->value); + } else if (!strcasecmp(var->name, "subscribe_event")) { + add_subscribe_event(event_channel, var->value); + } else { + ast_log(LOG_WARNING, "Event channel '%s' contains invalid option '%s'\n", + event_channel->name, var->name); + } + } + + AST_RWLIST_WRLOCK(&event_channels); + AST_RWLIST_INSERT_TAIL(&event_channels, event_channel, entry); + AST_RWLIST_UNLOCK(&event_channels); +} + +static void load_config(void) +{ + static const char filename[] = "ais.conf"; + struct ast_config *cfg; + const char *cat = NULL; + struct ast_flags config_flags = { 0 }; + + if (!(cfg = ast_config_load(filename, config_flags))) + return; + + while ((cat = ast_category_browse(cfg, cat))) { + const char *type; + + if (!strcasecmp(cat, "general")) + continue; + + if (!(type = ast_variable_retrieve(cfg, cat, "type"))) { + ast_log(LOG_WARNING, "Invalid entry in %s defined with no type!\n", + filename); + continue; + } + + if (!strcasecmp(type, "event_channel")) { + build_event_channel(cfg, cat); + } else { + ast_log(LOG_WARNING, "Entry in %s defined with invalid type '%s'\n", + filename, type); + } + } + + ast_config_destroy(cfg); +} + +static void publish_event_destroy(struct publish_event *publish_event) +{ + ast_event_unsubscribe(publish_event->sub); + + free(publish_event); +} + +static void subscribe_event_destroy(const struct event_channel *event_channel, + struct subscribe_event *subscribe_event) +{ + SaAisErrorT ais_res; + + /* saEvtChannelClose() will actually do this automatically, but it just + * feels cleaner to go ahead and do it manually ... */ + ais_res = saEvtEventUnsubscribe(event_channel->handle, subscribe_event->id); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error unsubscribing: %s\n", ais_err2str(ais_res)); + } + + free(subscribe_event); +} + +static void event_channel_destroy(struct event_channel *event_channel) +{ + struct publish_event *publish_event; + struct subscribe_event *subscribe_event; + SaAisErrorT ais_res; + + while ((publish_event = AST_LIST_REMOVE_HEAD(&event_channel->publish_events, entry))) + publish_event_destroy(publish_event); + while ((subscribe_event = AST_LIST_REMOVE_HEAD(&event_channel->subscribe_events, entry))) + subscribe_event_destroy(event_channel, subscribe_event); + + ais_res = saEvtChannelClose(event_channel->handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error closing event channel '%s': %s\n", + event_channel->name, ais_err2str(ais_res)); + } + + free(event_channel); +} + +static void destroy_event_channels(void) +{ + struct event_channel *event_channel; + + AST_RWLIST_WRLOCK(&event_channels); + while ((event_channel = AST_RWLIST_REMOVE_HEAD(&event_channels, entry))) + event_channel_destroy(event_channel); + AST_RWLIST_UNLOCK(&event_channels); +} + +int ast_ais_evt_load_module(void) +{ + SaAisErrorT ais_res; + + ais_res = saEvtInitialize(&evt_handle, &evt_callbacks, &ais_version); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Could not initialize eventing service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + load_config(); + + ast_cli_register_multiple(ais_cli, ARRAY_LEN(ais_cli)); + + return 0; +} + +int ast_ais_evt_unload_module(void) +{ + SaAisErrorT ais_res; + + destroy_event_channels(); + + ais_res = saEvtFinalize(evt_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Problem stopping eventing service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + return 0; +} diff --git a/res/ais/lck.c b/res/ais/lck.c new file mode 100644 index 0000000000000000000000000000000000000000..7e7533dc6845a7bb9e9c891a984b03ffc134202d --- /dev/null +++ b/res/ais/lck.c @@ -0,0 +1,551 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + * + * This file contains the code specific to the use of the LCK + * (Distributed Locks) Service. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "ais.h" + +#include "asterisk/module.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/logger.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/astobj2.h" +#include "asterisk/strings.h" +#include "asterisk/channel.h" + +SaLckHandleT lck_handle; + +/*! + * \brief Callbacks available in the Lock Service + * + * None of these are actually required if only synchronous locking is used. + * However, some of them must be implemented should the asynchronous locks + * be used. + */ +static SaLckCallbacksT lck_callbacks = { + /*! Get notified when a cluster-wide lock gets created */ + .saLckResourceOpenCallback = NULL, + /*! Get notified when an asynchronous lock request gets granted */ + .saLckLockGrantCallback = NULL, + /*! Be informed when a currently held lock is blocking another node */ + .saLckLockWaiterCallback = NULL, + /*! Get notified when an asynchronous unlock request is done */ + .saLckResourceUnlockCallback = NULL, +}; + +enum lock_type { + RDLOCK, + WRLOCK, + TRY_RDLOCK, + TRY_WRLOCK, +}; + +#define LOCK_BUCKETS 101 + +/*! + * Every thread that wants to use a distributed lock must open its own handle + * to the lock. So, a thread-local container of opened locks is used to keep + * track of what locks have been opened. + * + * \todo It would be nice to be able to have a thread-local container, instead + * of using a thread-local wrapper like this. + */ +struct lock_resources { + struct ao2_container *locks; +}; + +static int lock_resources_init(void *); +static void lock_resources_destroy(void *); + +AST_THREADSTORAGE_CUSTOM(locks_ts_key, + lock_resources_init, lock_resources_destroy); + +struct lock_resource { + SaLckResourceHandleT handle; + SaLckLockIdT id; + SaNameT ais_name; + const char *name; +}; + +static int lock_hash_cb(const void *obj, int flags) +{ + const struct lock_resource *lock = obj; + + return ast_str_hash(lock->name); +} + +static int lock_cmp_cb(void *obj, void *arg, int flags) +{ + struct lock_resource *lock1 = obj, *lock2 = arg; + + return !strcasecmp(lock1->name, lock2->name) ? CMP_MATCH : 0; +} + +static int lock_resources_init(void *data) +{ + struct lock_resources *lock_resources = data; + + if (!(lock_resources->locks = ao2_container_alloc(LOCK_BUCKETS, + lock_hash_cb, lock_cmp_cb))) { + return -1; + } + + return 0; + +} + +static void lock_resources_destroy(void *data) +{ + struct lock_resources *lock_resources = data; + + ao2_ref(lock_resources->locks, -1); + + ast_free(lock_resources); +} + +static void lock_destructor(void *obj) +{ + struct lock_resource *lock = obj; + + if (lock->name) + ast_free((void *) lock->name); +} + +static inline struct lock_resource *lock_ref(struct lock_resource *lock) +{ + ao2_ref(lock, +1); + return lock; +} + +static inline struct lock_resource *lock_unref(struct lock_resource *lock) +{ + ao2_ref(lock, -1); + return NULL; +} + +static void lock_datastore_destroy(void *data) +{ + struct lock_resource *lock = data; + SaAisErrorT ais_res; + + ais_res = saLckResourceUnlock(lock->id, SA_TIME_ONE_SECOND * 3); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error unlocking '%s': %s\n", lock->name, + ais_err2str(ais_res)); + } + + lock_unref(lock); +} + +static struct lock_resource *find_lock(const char *name) +{ + struct lock_resource *lock, tmp_lock = { + .name = name, + }; + SaAisErrorT ais_res; + struct lock_resources *lock_resources; + + if (!(lock_resources = ast_threadstorage_get(&locks_ts_key, + sizeof(*lock_resources)))) { + return NULL; + } + + /* Return the lock if it has already been opened by this thread */ + if ((lock = ao2_find(lock_resources->locks, &tmp_lock, OBJ_POINTER))) + return lock; + + /* Allocate and open the lock */ + if (!(lock = ao2_alloc(sizeof(*lock), lock_destructor))) + return NULL; + + if (!(lock->name = ast_strdup(name))) + return lock_unref(lock); + + /* Map the name into the SaNameT for convenience */ + ast_copy_string((char *) lock->ais_name.value, lock->name, + sizeof(lock->ais_name.value)); + lock->ais_name.length = strlen(lock->name); + + ais_res = saLckResourceOpen(lck_handle, &lock->ais_name, + SA_LCK_RESOURCE_CREATE, SA_TIME_ONE_SECOND * 3, &lock->handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Failed to open lock: %s\n", ais_err2str(ais_res)); + return lock_unref(lock); + } + + return lock; +} + +const struct ast_datastore_info dlock_datastore_info = { + .type = "DLOCK", + .destroy = lock_datastore_destroy, +}; + +static void add_lock_to_chan(struct ast_channel *chan, struct lock_resource *lock, + enum lock_type lock_type, double timeout, char *buf, size_t len) +{ + struct ast_datastore *datastore; + SaAisErrorT ais_res; + SaLckLockModeT mode = 0; + SaLckLockFlagsT flags = 0; + SaLckLockStatusT status; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &dlock_datastore_info, lock->name); + + if (datastore) { + ast_log(LOG_ERROR, "The DLOCK '%s' is already locked by channel '%s'\n", + lock->name, chan->name); + ast_channel_unlock(chan); + ast_copy_string(buf, "FAILURE", len); + return; + } + ast_channel_unlock(chan); + + switch (lock_type) { + case TRY_RDLOCK: + flags = SA_LCK_LOCK_NO_QUEUE; + mode = SA_LCK_PR_LOCK_MODE; + break; + case RDLOCK: + flags = SA_LCK_LOCK_NO_QUEUE; + mode = SA_LCK_PR_LOCK_MODE; + break; + case TRY_WRLOCK: + flags = SA_LCK_LOCK_NO_QUEUE; + mode = SA_LCK_EX_LOCK_MODE; + break; + case WRLOCK: + flags = SA_LCK_LOCK_NO_QUEUE; + mode = SA_LCK_EX_LOCK_MODE; + break; + } + + /* Actually acquire the lock now */ + ais_res = saLckResourceLock(lock->handle, &lock->id, mode, flags, 0, + (SaTimeT) timeout * SA_TIME_ONE_SECOND, &status); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Problem acquiring lock '%s': %s\n", + lock->name, ais_err2str(ais_res)); + ast_copy_string(buf, (ais_res == SA_AIS_ERR_TIMEOUT) ? "TIMEOUT" : + "FAILURE", len); + return; + } + + switch (status) { + case SA_LCK_LOCK_GRANTED: + ast_copy_string(buf, "SUCCESS", len); + break; + case SA_LCK_LOCK_DEADLOCK: + ast_copy_string(buf, "DEADLOCK", len); + return; + /*! XXX \todo Need to look at handling these other cases in a different way */ + case SA_LCK_LOCK_NOT_QUEUED: + case SA_LCK_LOCK_ORPHANED: + case SA_LCK_LOCK_NO_MORE: + case SA_LCK_LOCK_DUPLICATE_EX: + ast_copy_string(buf, "FAILURE", len); + return; + } + + if (!(datastore = ast_channel_datastore_alloc(&dlock_datastore_info, + lock->name))) { + ast_copy_string(buf, "FAILURE", len); + return; + } + + datastore->data = lock_ref(lock); + + ast_channel_lock(chan); + ast_channel_datastore_add(chan, datastore); + ast_channel_unlock(chan); +} + +static int handle_lock(struct ast_channel *chan, enum lock_type lock_type, + char *data, char *buf, size_t len) +{ + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(name); + AST_APP_ARG(timeout); + ); + int res = 0; + double timeout = 3; + struct lock_resource *lock = NULL; + + ast_autoservice_start(chan); + + AST_STANDARD_APP_ARGS(args, data); + if (ast_strlen_zero(args.name)) { + ast_log(LOG_ERROR, "The DLOCK functions require a lock name\n"); + res = -1; + goto return_cleanup; + } + switch (lock_type) { + case RDLOCK: + case WRLOCK: + if (!ast_strlen_zero(args.timeout) && ((timeout = atof(args.timeout)) < 0)) { + ast_log(LOG_ERROR, "Timeout value '%s' not valid\n", args.timeout); + res = -1; + goto return_cleanup; + } + break; + case TRY_RDLOCK: + case TRY_WRLOCK: + if (!ast_strlen_zero(args.timeout)) { + ast_log(LOG_ERROR, "The trylock functions only take one argument\n"); + res = -1; + goto return_cleanup; + } + } + + if (!(lock = find_lock(args.name))) { + ast_copy_string(buf, "FAILURE", len); + res = -1; + goto return_cleanup; + } + + add_lock_to_chan(chan, lock, lock_type, timeout, buf, len); + + lock = lock_unref(lock); + +return_cleanup: + ast_autoservice_stop(chan); + + return res; +} + +static int handle_rdlock(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + return handle_lock(chan, RDLOCK, data, buf, len); +} + +static int handle_wrlock(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + return handle_lock(chan, WRLOCK, data, buf, len); +} + +static int handle_tryrdlock(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + return handle_lock(chan, TRY_RDLOCK, data, buf, len); +} + +static int handle_trywrlock(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + return handle_lock(chan, TRY_WRLOCK, data, buf, len); +} + +static int handle_unlock(struct ast_channel *chan, const char *cmd, char *data, + char *buf, size_t len) +{ + struct ast_datastore *datastore; + struct lock_resource *lock; + SaAisErrorT ais_res; + int res = 0; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "DLOCK_UNLOCK requires a lock name\n"); + ast_copy_string(buf, "FAILURE", len); + return -1; + } + + ast_autoservice_start(chan); + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &dlock_datastore_info, data); + if (!datastore) { + ast_log(LOG_ERROR, "The DLOCK '%s' is not locked by channel '%s'\n", + data, chan->name); + ast_channel_unlock(chan); + ast_copy_string(buf, "FAILURE", len); + return -1; + } + ast_channel_datastore_remove(chan, datastore); + ast_channel_unlock(chan); + + lock = datastore->data; + ais_res = saLckResourceUnlock(lock->id, SA_TIME_ONE_SECOND * 3); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Error unlocking '%s': %s\n", lock->name, + ais_err2str(ais_res)); + res = -1; + ast_copy_string(buf, (ais_res == SA_AIS_ERR_TIMEOUT) ? "TIMEOUT" : + "FAILURE", len); + } else { + ast_copy_string(buf, "SUCCESS", len); + } + + datastore->data = lock_unref(lock); + ast_channel_datastore_free(datastore); + + ast_autoservice_stop(chan); + + return res; +} + +#define LOCK_DESC_COMMON \ +" The name of the lock can be anything. The first time a named lock gets\n" \ +"used, it will be automatically created and maintained amongst the cluster.\n" \ +" The result of this function will be one of the following:\n" \ +" SUCCESS | TIMEOUT | FAILURE | DEADLOCK\n" DEADLOCK_DESC + +#define DEADLOCK_DESC \ +" The result, DEADLOCK, can only be provided if the AIS implementation in\n" \ +"use provides the optional feature of deadlock detection. If the lock fails\n" \ +"with the result of DEADLOCK, it means that the AIS implementation has\n" \ +"determined that if this lock were acquired, it would cause a deadlock.\n" + +static struct ast_custom_function dlock_rdlock = { + .name = "DLOCK_RDLOCK", + .synopsis = "Read-lock a distributed lock", + .desc = +" This function will read-lock a distributed lock provided by the locking\n" +"service of AIS. This is a blocking operation. However, a timeout can be\n" +"specified to avoid deadlocks. The default timeout used if one is not\n" +"provided as an argument is 3 seconds.\n" +LOCK_DESC_COMMON +"", + .syntax = "DLOCK_RDLOCK(<lock_name>,[timeout])", + .read = handle_rdlock, +}; + +static struct ast_custom_function dlock_wrlock = { + .name = "DLOCK_WRLOCK", + .synopsis = "Write-lock a distributed lock", + .desc = +" This function will write-lock a distributed lock provided by the locking\n" +"service of AIS. This is a blocking operation. However, a timeout can be\n" +"specified to avoid deadlocks. The default timeout used if one is not\n" +"provided as an argument is 3 seconds.\n" +LOCK_DESC_COMMON +"", + .syntax = "DLOCK_WRLOCK(<lock_name>,[timeout])", + .read = handle_wrlock, +}; + +static struct ast_custom_function dlock_tryrdlock = { + .name = "DLOCK_TRYRDLOCK", + .synopsis = "Try to read-lock a distributed lock", + .desc = +" This function will attempt to read-lock a distributed lock provided by the\n" +"locking service of AIS. This is a non-blocking operation.\n" +" The name of the lock can be anything. The first time a named lock gets\n" +"used, it will be automatically created and maintained amongst the cluster.\n" +" The result of this function will be one of the following:\n" +" SUCCESS | FAILURE | DEADLOCK\n" +DEADLOCK_DESC +"", + .syntax = "DLOCK_TRYRDLOCK(<lock_name>)", + .read = handle_tryrdlock, +}; + +static struct ast_custom_function dlock_trywrlock = { + .name = "DLOCK_TRYWRLOCK", + .synopsis = "Try to write-lock a distributed lock", + .desc = +" This function will attempt to write-lock a distributed lock provided by\n" +"the locking service of AIS. This is a non-blocking operation.\n" +" The name of the lock can be anything. The first time a named lock gets\n" +"used, it will be automatically created and maintained amongst the cluster.\n" +" The result of this function will be one of the following:\n" +" SUCCESS | FAILURE | DEADLOCK\n" +DEADLOCK_DESC +"", + .syntax = "DLOCK_TRYWRLOCK(<lock_name>)", + .read = handle_trywrlock, +}; + +static struct ast_custom_function dlock_unlock = { + .name = "DLOCK_UNLOCK", + .synopsis = "Unlock a distributed lock", + .desc = +" This function will unlock a currently held distributed lock. This should\n" +"be used regardless of the lock was read or write locked. The result of\n" +"this funtion will be one of the following:\n" +" SUCCESS | TIMEOUT | FAILURE\n" +"", + .syntax = "DLOCK_UNLOCK(<lock_name>)", + .read = handle_unlock, +}; + +int ast_ais_lck_load_module(void) +{ + SaAisErrorT ais_res; + int res; + + ais_res = saLckInitialize(&lck_handle, &lck_callbacks, &ais_version); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Could not initialize distributed locking service: %s\n", + ais_err2str(ais_res)); + return -1; + } + + res = ast_custom_function_register(&dlock_rdlock); + res |= ast_custom_function_register(&dlock_wrlock); + res |= ast_custom_function_register(&dlock_tryrdlock); + res |= ast_custom_function_register(&dlock_trywrlock); + res |= ast_custom_function_register(&dlock_unlock); + + return res; +} + +int ast_ais_lck_unload_module(void) +{ + SaAisErrorT ais_res; + int res = 0; + + ast_custom_function_unregister(&dlock_rdlock); + ast_custom_function_unregister(&dlock_wrlock); + ast_custom_function_unregister(&dlock_tryrdlock); + ast_custom_function_unregister(&dlock_trywrlock); + ast_custom_function_unregister(&dlock_unlock); + + ais_res = saLckFinalize(lck_handle); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Problem stopping distributed locking service: %s\n", + ais_err2str(ais_res)); + res = -1; + } + + return res; +} diff --git a/res/res_ais.c b/res/res_ais.c new file mode 100644 index 0000000000000000000000000000000000000000..884eb0d230f59f6ae94c8c577bd44c90d59d1c2e --- /dev/null +++ b/res/res_ais.c @@ -0,0 +1,193 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2007 - 2008, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \author Russell Bryant <russell@digium.com> + * + * \brief Usage of the SAForum AIS (Application Interface Specification) + * + * \arg http://www.openais.org/ + * + * This file contains the common code between the uses of the different AIS + * services. + */ + +/*** MODULEINFO + <depend>SaClm</depend> + <depend>SaEvt</depend> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <pthread.h> + +#include "ais/ais.h" + +#include "asterisk/module.h" +#include "asterisk/options.h" +#include "asterisk/logger.h" +#include "asterisk/channel.h" +#include "asterisk/utils.h" +#include "asterisk/cli.h" + +static struct { + pthread_t id; + unsigned int stop:1; +} dispatch_thread = { + .id = AST_PTHREADT_NULL, +}; + +SaVersionT ais_version = { 'B', 1, 1 }; + +static const struct ais_error { + SaAisErrorT error; + const char *desc; +} ais_errors[] = { + { SA_AIS_OK, "OK" }, + { SA_AIS_ERR_LIBRARY, "Library Error" }, + { SA_AIS_ERR_VERSION, "Version Not Compatible" }, + { SA_AIS_ERR_INIT, "Callback Not Registered" }, + { SA_AIS_ERR_TIMEOUT, "Timeout" }, + { SA_AIS_ERR_TRY_AGAIN , "Try Again" }, + { SA_AIS_ERR_INVALID_PARAM, "Invalid Parameter" }, + { SA_AIS_ERR_NO_MEMORY, "No Memory" }, + { SA_AIS_ERR_BAD_HANDLE, "Invalid Handle" }, + { SA_AIS_ERR_BUSY, "Resource Already In Use" }, + { SA_AIS_ERR_ACCESS, "Access Denied" }, + { SA_AIS_ERR_NOT_EXIST, "Does Not Exist" }, + { SA_AIS_ERR_NAME_TOO_LONG, "Name Too Long" }, + { SA_AIS_ERR_EXIST, "Already Exists" }, + { SA_AIS_ERR_NO_SPACE, "Buffer Too Small" }, + { SA_AIS_ERR_INTERRUPT, "Request Interrupted" }, + { SA_AIS_ERR_NAME_NOT_FOUND, "Name Not Found" }, + { SA_AIS_ERR_NO_RESOURCES, "Not Enough Resources" }, + { SA_AIS_ERR_NOT_SUPPORTED, "Requested Function Not Supported" }, + { SA_AIS_ERR_BAD_OPERATION, "Operation Not Allowed" }, + { SA_AIS_ERR_FAILED_OPERATION, "Operation Failed" }, + { SA_AIS_ERR_MESSAGE_ERROR, "Communication Error" }, + { SA_AIS_ERR_QUEUE_FULL, "Destination Queue Full" }, + { SA_AIS_ERR_QUEUE_NOT_AVAILABLE, "Destination Queue Not Available" }, + { SA_AIS_ERR_BAD_FLAGS, "Invalid Flags" }, + { SA_AIS_ERR_TOO_BIG, "Value Too Large" }, + { SA_AIS_ERR_NO_SECTIONS, "No More Sections to Initialize" }, +}; + +const char *ais_err2str(SaAisErrorT error) +{ + int x; + + for (x = 0; x < ARRAY_LEN(ais_errors); x++) { + if (ais_errors[x].error == error) + return ais_errors[x].desc; + } + + return "Unknown"; +} + +static void *dispatch_thread_handler(void *data) +{ + SaSelectionObjectT clm_fd, evt_fd, max_fd; + int res; + fd_set read_fds; + SaAisErrorT ais_res; + + ais_res = saClmSelectionObjectGet(clm_handle, &clm_fd); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Failed to retrieve select fd for CLM service. " + "This module will not operate.\n"); + return NULL; + } + + ais_res = saEvtSelectionObjectGet(evt_handle, &evt_fd); + if (ais_res != SA_AIS_OK) { + ast_log(LOG_ERROR, "Failed to retrieve select fd for EVT service. " + "This module will not operate.\n"); + return NULL; + } + + max_fd = clm_fd > evt_fd ? clm_fd : evt_fd; + + while (!dispatch_thread.stop) { + FD_ZERO(&read_fds); + FD_SET(clm_fd, &read_fds); + FD_SET(evt_fd, &read_fds); + + res = ast_select(max_fd + 1, &read_fds, NULL, NULL, NULL); + if (res == -1 && errno != EINTR && errno != EAGAIN) { + ast_log(LOG_ERROR, "Select error (%s) dispatch thread going away now, " + "and the module will no longer operate.\n", strerror(errno)); + break; + } + + if (FD_ISSET(clm_fd, &read_fds)) + saClmDispatch(clm_handle, SA_DISPATCH_ALL); + if (FD_ISSET(evt_fd, &read_fds)) + saEvtDispatch(evt_handle, SA_DISPATCH_ALL); + } + + return NULL; +} + +static int load_module(void) +{ + if (ast_ais_clm_load_module()) + goto return_error; + + if (ast_ais_evt_load_module()) + goto evt_failed; + + if (ast_pthread_create_background(&dispatch_thread.id, NULL, + dispatch_thread_handler, NULL)) { + ast_log(LOG_ERROR, "Error starting AIS dispatch thread.\n"); + goto dispatch_failed; + } + + return AST_MODULE_LOAD_SUCCESS; + +dispatch_failed: + ast_ais_evt_unload_module(); +evt_failed: + ast_ais_clm_unload_module(); +return_error: + return AST_MODULE_LOAD_DECLINE; +} + +static int unload_module(void) +{ + ast_ais_clm_unload_module(); + ast_ais_evt_unload_module(); + + if (dispatch_thread.id != AST_PTHREADT_NULL) { + dispatch_thread.stop = 1; + pthread_kill(dispatch_thread.id, SIGURG); /* poke! */ + pthread_join(dispatch_thread.id, NULL); + } + + return 0; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SAForum AIS");