amqp plugin: Let the user chose the delivery method.
[collectd.git] / src / amqp.c
1 /*
2 **
3 ** collectd-amqp
4 ** Copyright (c) <2009> <sebastien.pahl@dotcloud.com>
5 **
6 ** Permission is hereby granted, free of charge, to any person
7 ** obtaining a copy of this software and associated documentation
8 ** files (the "Software"), to deal in the Software without
9 ** restriction, including without limitation the rights to use,
10 ** copy, modify, merge, publish, distribute, sublicense, and/or sell
11 ** copies of the Software, and to permit persons to whom the
12 ** Software is furnished to do so, subject to the following
13 ** conditions:
14 **
15 ** The above copyright notice and this permission notice shall be
16 ** included in all copies or substantial portions of the Software.
17 **
18 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 ** OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 ** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 ** HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 ** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 ** OTHER DEALINGS IN THE SOFTWARE.
26 **
27 */
28
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <strings.h>
33 #include <pthread.h>
34
35 #include "collectd.h"
36 #include "common.h"
37 #include "plugin.h"
38 #include "utils_format_json.h"
39
40 #include <amqp.h>
41 #include <amqp_framing.h>
42
43 /* Defines for the delivery mode. I have no idea why they're not defined by the
44  * library.. */
45 #define AMQP_DM_VOLATILE   1
46 #define AMQP_DM_PERSISTENT 2
47
48 static int  port;
49 static char *host       = NULL;
50 static char *vhost      = NULL;
51 static char *user       = NULL;
52 static char *password   = NULL;
53 static char *exchange   = NULL;
54 static char *routingkey = NULL;
55 static uint8_t delivery_mode = AMQP_DM_VOLATILE;
56
57 static amqp_connection_state_t amqp_conn = NULL;
58 static pthread_mutex_t amqp_conn_lock = PTHREAD_MUTEX_INITIALIZER;
59
60 static const char *config_keys[] =
61 {
62     "Host",
63     "Port",
64     "VHost",
65     "User",
66     "Password",
67     "Exchange",
68     "RoutingKey",
69     "Persistent"
70 };
71
72 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
73
74 static int config_set(char **var, const char *value)
75 {
76     sfree(*var);
77     if ((*var = strdup(value)) == NULL)
78         return (1);
79     return (0);
80 }
81
82 static int config(const char *key, const char *value)
83 {
84     if (strcasecmp(key, "host") == 0)
85         return (config_set(&host, value));
86     else if(strcasecmp(key, "port") == 0)
87     {
88         int tmp;
89
90         tmp = service_name_to_port_number (value);
91         if (tmp <= 0)
92         {
93             ERROR ("AMQP plugin: Cannot parse `%s' as a "
94                     "service name (port number).", value);
95             return (1);
96         }
97
98         port = tmp;
99         return (0);
100     }
101     else if (strcasecmp(key, "vhost") == 0)
102         return (config_set(&vhost, value));
103     else if (strcasecmp(key, "user") == 0)
104         return (config_set(&user, value));
105     else if (strcasecmp(key, "password") == 0)
106         return (config_set(&password, value));
107     else if (strcasecmp(key, "exchange") == 0)
108         return (config_set(&exchange, value));
109     else if (strcasecmp(key, "routingkey") == 0)
110         return (config_set(&routingkey, value));
111     else if (strcasecmp ("Persistent", key) == 0)
112     {
113         if (IS_TRUE (value))
114             delivery_mode = AMQP_DM_PERSISTENT;
115         else
116             delivery_mode = AMQP_DM_VOLATILE;
117         return (0);
118     }
119     return (-1);
120 }
121
122 static int amqp_write_locked (const char *buffer)
123 {
124     amqp_rpc_reply_t reply;
125     amqp_basic_properties_t props;
126     int status;
127
128     if (amqp_conn == NULL)
129     {
130         int sockfd;
131
132         amqp_conn = amqp_new_connection ();
133         if (amqp_conn == NULL)
134         {
135             ERROR ("amqp plugin: amqp_new_connection failed.");
136             return (ENOMEM);
137         }
138
139         sockfd = amqp_open_socket (host, port);
140         if (sockfd < 0)
141         {
142             char errbuf[1024];
143             status = (-1) * sockfd;
144             ERROR ("amqp plugin: amqp_open_socket failed: %s",
145                     sstrerror (status, errbuf, sizeof (errbuf)));
146             amqp_destroy_connection(amqp_conn);
147             amqp_conn = NULL;
148             return (status);
149         }
150
151         amqp_set_sockfd (amqp_conn, sockfd);
152
153         reply = amqp_login(amqp_conn, vhost,
154                 /* channel max = */      0,
155                 /* frame max = */   131072,
156                 /* heartbeat = */        0,
157                 /* authentication: */ AMQP_SASL_METHOD_PLAIN, user, password);
158         if (reply.reply_type != AMQP_RESPONSE_NORMAL)
159         {
160             ERROR ("amqp plugin: amqp_login (vhost = %s, user = %s) failed.",
161                     vhost, user);
162             amqp_destroy_connection(amqp_conn);
163             close(sockfd);
164             amqp_conn = NULL;
165             return (1);
166         }
167
168         amqp_channel_open (amqp_conn, /* channel = */ 1);
169         /* FIXME: Is checking "reply.reply_type" really correct here? How does
170          * it get set? --octo */
171         if (reply.reply_type != AMQP_RESPONSE_NORMAL)
172         {
173             ERROR ("amqp plugin: amqp_channel_open failed.");
174             amqp_connection_close (amqp_conn, AMQP_REPLY_SUCCESS);
175             amqp_destroy_connection(amqp_conn);
176             close(sockfd);
177             amqp_conn = NULL;
178             return (1);
179         }
180
181         INFO ("amqp plugin: Successfully opened connection to vhost \"%s\" "
182                 "on %s:%i.", vhost, host, port);
183     } /* if (amqp_conn == NULL) */
184
185     props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG;
186     props.content_type = amqp_cstring_bytes("application/json");
187     props.delivery_mode = delivery_mode;
188
189     status = amqp_basic_publish(amqp_conn,
190                 /* channel = */ 1,
191                 amqp_cstring_bytes(exchange),
192                 amqp_cstring_bytes(routingkey),
193                 /* mandatory = */ 0,
194                 /* immediate = */ 0,
195                 &props,
196                 amqp_cstring_bytes(buffer));
197     if (status != 0)
198     {
199         int sockfd;
200
201         ERROR ("amqp plugin: amqp_basic_publish failed with status %i.",
202                 status);
203
204         sockfd = amqp_get_sockfd (amqp_conn);
205         amqp_channel_close (amqp_conn, 1, AMQP_REPLY_SUCCESS);
206         amqp_connection_close (amqp_conn, AMQP_REPLY_SUCCESS);
207         amqp_destroy_connection (amqp_conn);
208         close(sockfd);
209         amqp_conn = NULL;
210     }
211
212     return (status);
213 } /* int amqp_write_locked */
214
215 static int amqp_write (const data_set_t *ds, const value_list_t *vl,
216         __attribute__((unused)) user_data_t *user_data)
217 {
218     char buffer[4096];
219     size_t bfree;
220     size_t bfill;
221     int status;
222
223     if ((ds == NULL) || (vl == NULL))
224         return (EINVAL);
225
226     memset (buffer, 0, sizeof (buffer));
227     bfree = sizeof (buffer);
228     bfill = 0;
229
230     format_json_initialize(buffer, &bfill, &bfree);
231     /* TODO: Possibly add a config option "StoreRates" and pass the value along here. */
232     format_json_value_list(buffer, &bfill, &bfree, ds, vl, /* rates = */ 0);
233     format_json_finalize(buffer, &bfill, &bfree);
234
235     pthread_mutex_lock (&amqp_conn_lock);
236     status = amqp_write_locked (buffer);
237     pthread_mutex_unlock (&amqp_conn_lock);
238
239     return (status);
240 } /* int amqp_write */
241
242 static int shutdown(void)
243 {
244     pthread_mutex_lock (&amqp_conn_lock);
245     if (amqp_conn != NULL)
246     {
247         int sockfd;
248
249         sockfd = amqp_get_sockfd (amqp_conn);
250         amqp_channel_close (amqp_conn, 1, AMQP_REPLY_SUCCESS);
251         amqp_connection_close (amqp_conn, AMQP_REPLY_SUCCESS);
252         amqp_destroy_connection (amqp_conn);
253         close(sockfd);
254         amqp_conn = NULL;
255     }
256     pthread_mutex_unlock (&amqp_conn_lock);
257
258     sfree(host);
259     sfree(vhost);
260     sfree(user);
261     sfree(password);
262     sfree(exchange);
263     sfree(routingkey);
264
265     return (0);
266 }
267
268 void module_register(void)
269 {
270     plugin_register_config("amqp", config, config_keys, config_keys_num);
271     plugin_register_write("amqp", amqp_write, NULL);
272     plugin_register_shutdown("amqp", shutdown);
273 }
274
275 /* vim: set sw=4 sts=4 et : */