OVS link: Implement OVS link plugin
[collectd.git] / src / ovs_link.c
1 /**
2  * collectd - src/ovs_link.c
3  *
4  * Copyright(c) 2016 Intel Corporation. All rights reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy of
7  * this software and associated documentation files (the "Software"), to deal in
8  * the Software without restriction, including without limitation the rights to
9  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10  * of the Software, and to permit persons to whom the Software is furnished to do
11  * so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  *
24  * Authors:
25  *   Volodymyr Mytnyk <volodymyrx.mytnyk@intel.com>
26  **/
27
28 #include "common.h"             /* auxiliary functions */
29 #include "utils_ovs.h"          /* OVS helpers */
30
31 #define OVS_LINK_PLUGIN "ovs_link"
32 #define OVS_LINK_DEFAULT_OVS_DB_SERVER_URL "tcp:127.0.0.1:6640"
33 #define CONFIG_LOCK for (int __i = config_lock(); __i != 0 ; \
34                          __i = config_unlock())
35
36 struct interface_s {
37   char *name;                   /* interface name */
38   struct interface_s *next;     /* next interface name */
39 };
40 typedef struct interface_s interface_t;
41
42 struct ovs_link_config_s {
43   pthread_mutex_t mutex;        /* mutex to lock the config structure */
44   char *ovs_db_server_url;      /* OVS DB server URL */
45   ovs_db_t *ovs_db;             /* pointer to OVS DB instance */
46   interface_t *ifaces;          /* interface names */
47 };
48 typedef struct ovs_link_config_s ovs_link_config_t;
49
50 /*
51  * Private variables
52  */
53 ovs_link_config_t config = {PTHREAD_MUTEX_INITIALIZER, NULL, NULL, NULL};
54
55 /* This function is used only by "CONFIG_LOCK" defined above.
56  * It always returns 1 when the config is locked.
57  */
58 static inline int
59 config_lock()
60 {
61   pthread_mutex_lock(&config.mutex);
62   return (1);
63 }
64
65 /* This function is used only by "CONFIG_LOCK" defined above.
66  * It always returns 0 when config is unlocked.
67  */
68 static inline int
69 config_unlock()
70 {
71   pthread_mutex_unlock(&config.mutex);
72   return (0);
73 }
74
75 /* Check if given interface name exists in configuration file. It
76  * returns 1 if exists otherwise 0. If no interfaces are configured,
77  * 1 is returned
78  */
79 static int
80 ovs_link_config_iface_exists(const char *ifname)
81 {
82   int rc = 0;
83   CONFIG_LOCK {
84     if (!(rc = (config.ifaces == NULL))) {
85       for (interface_t *iface = config.ifaces; iface; iface = iface->next)
86         if (rc = (strcmp(ifname, iface->name) == 0))
87           break;
88     }
89   }
90   return rc;
91 }
92
93 /* Release memory allocated for configuration data */
94 static void
95 ovs_link_config_free()
96 {
97   interface_t *del_iface = NULL;
98   CONFIG_LOCK {
99     sfree(config.ovs_db_server_url);
100     while (config.ifaces) {
101       del_iface = config.ifaces;
102       config.ifaces = config.ifaces->next;
103       free(del_iface->name);
104       free(del_iface);
105     }
106   }
107 }
108
109 /* Parse plugin configuration file and store the config
110  * in allocated memory. Returns negative value in case of error.
111  */
112 static int
113 ovs_link_plugin_config(oconfig_item_t *ci)
114 {
115   interface_t *new_iface;
116   char *if_name;
117   char *ovs_db_url;
118
119   for (int i = 0; i < ci->children_num; i++) {
120     oconfig_item_t *child = ci->children + i;
121     if (strcasecmp("OvsDbServerUrl", child->key) == 0) {
122       if (cf_util_get_string(child, &ovs_db_url) < 0) {
123         ERROR(OVS_LINK_PLUGIN ": parse '%s' option failed", child->key);
124         goto failure;
125       } else
126         config.ovs_db_server_url = ovs_db_url;
127     } else if (strcasecmp("Interfaces", child->key) == 0) {
128       for (int j = 0; j < child->values_num; j++) {
129         /* check value type */
130         if (child->values[j].type != OCONFIG_TYPE_STRING) {
131           ERROR(OVS_LINK_PLUGIN
132                 ": given interface name is not a string [idx=%d]", j);
133           goto failure;
134         }
135
136         /* get value */
137         if ((if_name = strdup(child->values[j].value.string)) == NULL) {
138           ERROR(OVS_LINK_PLUGIN " strdup() copy interface name fail");
139           goto failure;
140         }
141
142         if ((new_iface = malloc(sizeof(*new_iface))) == NULL) {
143           ERROR(OVS_LINK_PLUGIN ": malloc () copy interface name fail");
144           goto failure;
145         } else {
146           /* store interface name */
147           new_iface->name = if_name;
148           new_iface->next = config.ifaces;
149           CONFIG_LOCK {
150             config.ifaces = new_iface;
151           }
152           DEBUG(OVS_LINK_PLUGIN ": found monitored interface \"%s\"",
153                 if_name);
154         }
155       }
156     } else {
157       ERROR(OVS_LINK_PLUGIN ": option '%s' is not allowed here", child->key);
158       goto failure;
159     }
160   }
161   return (0);
162
163 failure:
164   ovs_link_config_free();
165   return (-1);
166 }
167
168 /* Dispatch OVS interface link status event to collectd */
169 static int
170 ovs_link_dispatch_notification(const char *link_name, const char *link_state)
171 {
172   notification_t n = {NOTIF_FAILURE, time(NULL), "", "", OVS_LINK_PLUGIN,
173                       "", "", "", NULL};
174
175   /* fill the notification data */
176   if (link_state != NULL)
177     n.severity = ((strcmp(link_state, "up") == 0) ?
178                   NOTIF_OKAY : NOTIF_WARNING);
179   else
180     link_state = "UNKNOWN";
181
182   sstrncpy(n.host, hostname_g, sizeof(n.host));
183   ssnprintf(n.message, sizeof(n.message),
184             "link state of \"%s\" interface has been changed to \"%s\"",
185             link_name, link_state);
186
187   /* send the notification */
188   return plugin_dispatch_notification(&n);
189 }
190
191 /* Process OVS DB update table event. It handles link status update event(s)
192  * and dispatches the value(s) to collectd if interface name matches one of
193  * interfaces specified in configuration file.
194  */
195 static void
196 ovs_link_table_update_cb(yajl_val jupdates)
197 {
198   yajl_val jnew_val = NULL;
199   yajl_val jupdate = NULL;
200   yajl_val jrow_update = NULL;
201   yajl_val jlink_name = NULL;
202   yajl_val jlink_state = NULL;
203   const char *link_name = NULL;
204
205   /* JSON "Interface" table update example:
206    * ---------------------------------
207    * {"Interface":
208    *  {
209    *   "9adf1db2-29ca-4140-ab22-ae347a4484de":
210    *    {
211    *     "new":
212    *      {
213    *       "name":"br0",
214    *       "link_state":"up"
215    *      },
216    *     "old":
217    *      {
218    *       "link_state":"down"
219    *      }
220    *    }
221    *  }
222    * }
223    */
224   if (!YAJL_IS_OBJECT(jupdates) || !(YAJL_GET_OBJECT(jupdates)->len > 0)) {
225     ERROR(OVS_LINK_PLUGIN ": unexpected OVS DB update event received");
226     return;
227   }
228   /* verify if this is a table event */
229   jupdate = YAJL_GET_OBJECT(jupdates)->values[0];
230   if (!YAJL_IS_OBJECT(jupdate)) {
231     ERROR(OVS_LINK_PLUGIN ": unexpected table update event received");
232     return;
233   }
234   /* go through all row updates  */
235   for (int row_index = 0; row_index < YAJL_GET_OBJECT(jupdate)->len;
236        ++row_index) {
237     jrow_update = YAJL_GET_OBJECT(jupdate)->values[row_index];
238
239     /* check row update */
240     jnew_val = ovs_utils_get_value_by_key(jrow_update, "new");
241     if (jnew_val == NULL) {
242       ERROR(OVS_LINK_PLUGIN ": unexpected row update received");
243       return;
244     }
245     /* get link status update */
246     jlink_name = ovs_utils_get_value_by_key(jnew_val, "name");
247     jlink_state = ovs_utils_get_value_by_key(jnew_val, "link_state");
248     if (jlink_name && jlink_state) {
249       link_name = YAJL_GET_STRING(jlink_name);
250       if (link_name && ovs_link_config_iface_exists(link_name)) {
251         /* dispatch notification */
252         ovs_link_dispatch_notification(link_name,
253                                        YAJL_GET_STRING(jlink_state));
254       }
255     }
256   }
257 }
258
259 /* Process OVS DB result table callback. It handles init link status value
260  * and dispatches the value(s) to collectd. The logic to handle init status
261  * is same as 'ovs_link_table_update_cb'.
262  */
263 static void
264 ovs_link_table_result_cb(yajl_val jresult, yajl_val jerror)
265 {
266   (void)jerror;
267   /* jerror is not used as it is the same all the time
268      (rfc7047, "Monitor" section, return value) */
269   ovs_link_table_update_cb(jresult);
270 }
271
272 /* Setup OVS DB table callback. It subscribes to 'Interface' tables
273  * to receive link status events.
274  */
275 static void
276 ovs_link_initialize(ovs_db_t *pdb)
277 {
278   int ret = 0;
279   const char tb_name[] = "Interface";
280   const char *columns[] = {"name", "link_state", NULL};
281
282   /* register the update callback */
283   ret = ovs_db_table_cb_register(pdb, tb_name, columns,
284                                  ovs_link_table_update_cb,
285                                  ovs_link_table_result_cb,
286                                  OVS_DB_TABLE_CB_FLAG_MODIFY |
287                                  OVS_DB_TABLE_CB_FLAG_INITIAL);
288   if (ret < 0) {
289     ERROR(OVS_LINK_PLUGIN ": register OVS DB update callback failed");
290     return;
291   }
292
293   DEBUG(OVS_LINK_PLUGIN ": OVS DB has been initialized");
294 }
295
296 /* Set default config values (update config) if some of them aren't
297  * specified in configuration file
298  */
299 static inline int
300 ovs_link_config_set_default()
301 {
302   if (!config.ovs_db_server_url)
303     config.ovs_db_server_url = strdup(OVS_LINK_DEFAULT_OVS_DB_SERVER_URL);
304   return (config.ovs_db_server_url == NULL);
305 }
306
307 /* Initialize OVS plugin */
308 static int
309 ovs_link_plugin_init(void)
310 {
311   ovs_db_t *ovs_db = NULL;
312   ovs_db_callback_t cb = {.init_cb = ovs_link_initialize};
313
314   if (ovs_link_config_set_default()) {
315     ERROR(OVS_LINK_PLUGIN ": fail to make configuration");
316     ovs_link_config_free();
317     return (-1);
318   }
319
320   /* initialize OVS DB */
321   if ((ovs_db = ovs_db_init(config.ovs_db_server_url, &cb)) == NULL) {
322     ERROR(OVS_LINK_PLUGIN ": fail to connect to OVS DB server");
323     ovs_link_config_free();
324     return (-1);
325   }
326
327   /* store OVSDB handler */
328   CONFIG_LOCK {
329     config.ovs_db = ovs_db;
330   }
331
332   DEBUG(OVS_LINK_PLUGIN ": plugin has been initialized");
333   return (0);
334 }
335
336 /* Shutdown OVS plugin */
337 static int
338 ovs_link_plugin_shutdown(void)
339 {
340   /* release memory allocated for config */
341   ovs_link_config_free();
342
343   /* destroy OVS DB */
344   if (ovs_db_destroy(config.ovs_db))
345     ERROR(OVS_LINK_PLUGIN ": OVSDB object destroy failed");
346
347   DEBUG(OVS_LINK_PLUGIN ": plugin has been destroyed");
348   return (0);
349 }
350
351 /* Register OVS plugin callbacks */
352 void
353 module_register(void)
354 {
355   plugin_register_complex_config(OVS_LINK_PLUGIN, ovs_link_plugin_config);
356   plugin_register_init(OVS_LINK_PLUGIN, ovs_link_plugin_init);
357   plugin_register_shutdown(OVS_LINK_PLUGIN, ovs_link_plugin_shutdown);
358 }