diff options
author | Roman Rakus <rrakus@redhat.com> | 2013-05-27 13:08:04 +0200 |
---|---|---|
committer | Roman Rakus <rrakus@redhat.com> | 2013-05-27 14:48:18 +0200 |
commit | d6149b81861a54aa6502778f1027e5868885b333 (patch) | |
tree | 0329ff17e860dfaaedd4c8561fb7f00ea26be00d | |
parent | 46b79c42c4235e597d3a5e9ce7329f510c3858ec (diff) | |
download | openlmi-providers-d6149b81861a54aa6502778f1027e5868885b333.tar.gz openlmi-providers-d6149b81861a54aa6502778f1027e5868885b333.tar.xz openlmi-providers-d6149b81861a54aa6502778f1027e5868885b333.zip |
Indication manager
Signed-off-by: Roman Rakus <rrakus@redhat.com>
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/indmanager/CMakeLists.txt | 19 | ||||
-rw-r--r-- | src/indmanager/README | 124 | ||||
-rw-r--r-- | src/indmanager/ind_manager.c | 1105 | ||||
-rw-r--r-- | src/indmanager/ind_manager.h | 196 | ||||
-rw-r--r-- | src/indmanager/openlmiindmanager.pc.in | 10 |
7 files changed, 1459 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5722619..c5a5c7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ option(WITH-ACCOUNT "Build account provider" ON) option(WITH-HARDWARE "Build hardware provider" ON) option(WITH-LOGICALFILE "Build logical file provider" ON) option(WITH-REALMD "Build RealmD provider" ON) +option(WITH-INDMANAGER "Build indication manager" ON) # Set path to custom cmake modules set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9f714d0..27bd416 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,3 +44,7 @@ endif (WITH-LOGICALFILE) if (WITH-REALMD) add_subdirectory(realmd) endif (WITH-REALMD) + +if (WITH-INDMANAGER) + add_subdirectory(indmanager) +endif (WITH-INDMANAGER) diff --git a/src/indmanager/CMakeLists.txt b/src/indmanager/CMakeLists.txt new file mode 100644 index 0000000..4ae0963 --- /dev/null +++ b/src/indmanager/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(openlmiindmanager SHARED + ind_manager.c +) + +set(OPENLMIINDMANAGER_VERSION_MAJOR 0) +set(OPENLMIINDMANAGER_VERSION_MINOR 0) +set(OPENLMIINDMANAGER_VERSION_PATCH 1) +set(OPENLMIINDMANAGER_VERSION "${OPENLMIINDMANAGER_VERSION_MAJOR}.${OPENLMIINDMANAGER_VERSION_MINOR}.${OPENLMIINDMANAGER_VERSION_PATCH}") + +set_target_properties(openlmiindmanager PROPERTIES VERSION ${OPENLMIINDMANAGER_VERSION}) +set_target_properties(openlmiindmanager PROPERTIES SOVERSION ${OPENLMIINDMANAGER_VERSION_MAJOR}) + +target_link_libraries(openlmiindmanager pthread) + +install(TARGETS openlmiindmanager DESTINATION lib${LIB_SUFFIX}) +install(FILES ind_manager.h DESTINATION include/openlmi) + +configure_file(openlmiindmanager.pc.in openlmiindmanager.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/openlmiindmanager.pc DESTINATION lib${LIB_SUFFIX}/pkgconfig) diff --git a/src/indmanager/README b/src/indmanager/README new file mode 100644 index 0000000..d1efb8c --- /dev/null +++ b/src/indmanager/README @@ -0,0 +1,124 @@ +###################### +# Indication manager # +###################### + in short IM + +Purpose +~~~~~~~ +Indication manager (in short IM) should help in writing indication provider +using CMPI. Main concept is to lessen work you need to do to only define +few callback functions. + +Usage +~~~~~ +Mainly the user of IM have to write callback functions: IMFilterChecker, +IMEventWatcher. +IM can be used to poll instance. This has to be set upon initialization +and cannot be changed. When not using polling the user have to write also +IMInstGather callback function. +For user are provided the following functions: +im_create_manager, im_destroy_manager, im_verify_filter, im_add_filter, +im_remove_filter, im_start_ind and im_stop_ind. +Following are documentation for functions and callback definitions, +in the following form: +* name of function or callback +C definition +Documentation. + +* im_create_manager +IMManager* im_create_manager(IMInstGather gather, IMFilterChecker f_checker, + bool polling, IMEventWatcher watcher, + IMIndType type, CMPIBroker *broker, IMError *err); +Return newly created reference to IMManager. User should use this reference +only as parameter for functions. User should provide callback functions (see +below), should choose if want to use polling, choose type indications and +provide broker reference. On any error occurance the NULL is returned and +err is set. + +* im_destroy_manager +bool im_destroy_manager(IMManager *manager, const CMPIContext *ctx, + IMError *err); +Destroy the given manager. User should provide context. Returns true when +everything was correctly done, or false and err is set. + +* im_verify_filter +bool im_verify_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err); +Ask manager to verify the given filter. User should provide context. In turn, +the manager calls IMFilterChecker (see below). If filter is accepted true +is returned, otherwise false is returned and err is set. + +* im_add_filter +bool im_add_filter(IMManager *manager, CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err); +Adds the given filter to the manager. It is logical error to add filter, which +is not verified before. User should provide context. Return true when everything +is correctly done, false otherwise and err is set. + +* im_remove_filter +bool im_remove_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err); +Removes the given filter from the manager. Returns true when correctly done, +or false and err is set. + +* im_start_ind +bool im_start_ind(IMManager *manager, const CMPIContext *ctx, IMError *err); +Informs the manager to start sending indications. It is recursively calling +IMEventWatcher and IMInstGather (if using polling, the default gather is used). +Returns true if everything started correctly, false otherwise and err is set. + +* im_stop_ind +bool im_stop_ind(IMManager *manager, const CMPIContext *ctx, IMError *err); +Informs the manager to stop sending indications. Returns true when everything +is done correctly, false otherwise and err is set. + +* IMFilterChecker +typedef bool (*IMFilterChecker) (const CMPISelectExp *filter); +User should define this callback, which is called whenever manager is asked +to verify filter. Function should return true when given filter is allowed, +false otherwise. Filter is passed from im_verify_filter function + +* IMEventWatcher +typedef bool (*IMEventWatcher) (void *data); +User should define this callback, which could be blocking. The purpose of this +called is to inform manager that event occured. When the callback returns with +true value the manager is informed that event has occured and will send +indication right after. When the callback returns with false value the manager +is informed that event hasn't occured and will not send indication and will +retart the loop - the callback will be executed again soon. +User can use data to transfer any information from this callback +to IMInstGather. + +* IMInstGather +typedef bool (*IMInstGather) (CMPIInstance **old, CMPIInstance **new, void *data); +This callback has to be defined only when not using default polling mechanism. +data are passed from IMEventWatcher and are supposed to help creating instances. +User should create and set instance and/or instances. When type of indications +is IM_IND_CREATION the new instance has to be set. When IM_IND_DELETION the old +instance has to be set. Both when IM_IND_MODIFICATION is used. When the callback +returns true the manager will send the indication and call the callback again. +In other words, the return value means "There is another indication +with instances to be send". When false is returned the manager will send +the last indication and starts another round in the loop. + +Polling +~~~~~~~ +IM supports basic polling mechanism. When enabled the IM remembers instances +after every IMEventWatcher call and default IMInstGather callback is used. +Which instances are to be polled are determined by the filters, where +the IM looks for the first occurence of ISA predicate in WHERE clause of select +expression. For example we have filter with expression: +SELECT * FROM LMI_SomeIndication WHERE SourceInstance ISA LMI_Account +the manager will be polling instances from LMI_Account class (from the same +namespace as LMI_SomeIndication). Polling is possible on multiple classes. + +Known limitations +~~~~~~~~~~~~~~~~~ +IM supports only instance indications of the following types: +Creation, Deletion, Modification +and supports only one type per provider. +Settings cannot be changed. + +Example +~~~~~~~ +Is not provided yet. diff --git a/src/indmanager/ind_manager.c b/src/indmanager/ind_manager.c new file mode 100644 index 0000000..6db2a4c --- /dev/null +++ b/src/indmanager/ind_manager.c @@ -0,0 +1,1105 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Roman Rakus <rrakus@redhat.com> + */ + +#include "ind_manager.h" + +#include <cmpi/cmpidt.h> +#include <cmpi/cmpift.h> +#include <cmpi/cmpimacs.h> + +#include <string.h> +#include <stdio.h> +#include <pthread.h> +#include <time.h> + +/* Debugging macros */ +#define DEBUG(...) \ + _im_debug(manager->broker, __FILE__, __func__, __LINE__, __VA_ARGS__) +#include <stdarg.h> + +static inline void _im_debug(CMPIBroker *b, const char *file, const char *func, + int line, const char *format, ...) +{ + char text[2048] = ""; + snprintf(text, 2047, "%s:%d(%s):", file, line, func); + va_list args; + va_start(args, format); + vsnprintf(text+strlen(text), 2047-strlen(text), format, args); + va_end(args); + CMLogMessage(b, CMPI_DEV_DEBUG, "LMI indication manager", text, NULL); +} + +// Get char * representation of name space from object path +#define NS_STR_FROM_OP(op) CMGetCharPtr(CMGetNameSpace((op), NULL)) + +// Get char * representation of class name from object path +#define CN_STR_FROM_OP(op) CMGetCharPtr(CMGetClassName((op), NULL)) + +typedef struct _IMPolledDiff { + CMPIInstance *iold; + CMPIInstance *inew; + struct _IMPolledDiff *next; +} IMPolledDiff; + +typedef struct _IMPolledDiffs { + IMPolledDiff *first; +} IMPolledDiffs; + +typedef enum _IMIDiff { + IM_I_SAME, // Instances are the same + IM_I_DIFFERENT, // Instances are different in key properties or value types + IM_I_CHANGE // Instances are different in non-key properties +} IMIDiff; + +IMIDiff compare_instances(CMPIInstance *, CMPIInstance *); +bool maybe_remove_polled(IMManager *, CMPIObjectPath *, IMError *); + +/* For threading purposes */ +pthread_t _t_manage; +pthread_mutex_t _t_mutex; +pthread_cond_t _t_cond; + +/* CMPI rc handler */ +CMPIStatus im_rc; + +/* + * Compare 2 object paths + * return true if they are same, false otherwise + * is using ft->toString() + */ +bool compare_objectpaths(CMPIObjectPath *op1, CMPIObjectPath *op2) +{ + return (strcmp(CMGetCharsPtr(CMObjectPathToString(op1, NULL), NULL), + CMGetCharsPtr(CMObjectPathToString(op2, NULL), NULL)) == 0); +} + +/* + * Compare 2 cmpi data + * return true if they are the same, false otherwise + * note: inspired by value.c:sfcb_comp_CMPIValue() from sblim-sfcb + */ +bool compare_values(CMPIValue *v1, CMPIValue *v2, CMPIType type) +{ +// Checks for NULL, but it is incorrect +#define CHECK_PTRS \ + if (!v1->array && !v2->array) {\ + return true;\ + }\ + if (!v1->array || !v2->array) {\ + return false;\ + }\ + + if (type & CMPI_ARRAY) { + CHECK_PTRS; + CMPICount c = CMGetArrayCount(v1->array, NULL); + if (c != CMGetArrayCount(v2->array, NULL)) { + return false; + } + for (; c > 0; c--) { + CMPIValue val1 = CMGetArrayElementAt(v1->array, c-1, NULL).value; + CMPIValue val2 = CMGetArrayElementAt(v2->array, c-1, NULL).value; + if (!compare_values(&val1, &val2, type & ~CMPI_ARRAY)) { + return false; + } + } + return true; + } + switch (type) { + case CMPI_boolean: + case CMPI_uint8: + case CMPI_sint8: + return (v1->Byte == v2->Byte); + case CMPI_char16: + case CMPI_uint16: + case CMPI_sint16: + return (v1->Short == v2->Short); + case CMPI_uint32: + case CMPI_sint32: + return (v1->Int == v2->Int); + case CMPI_real32: + return (v1->Float == v2->Float); + case CMPI_uint64: + case CMPI_sint64: + return (v1->Long == v2->Long); + case CMPI_real64: + return(v1->Double == v2->Double); + case CMPI_instance: + CHECK_PTRS; + return (compare_instances(v1->inst, v2->inst) == IM_I_SAME); + case CMPI_ref: + CHECK_PTRS; + return (compare_objectpaths(v1->ref, v2->ref)); + case CMPI_string: + CHECK_PTRS; + return (strcmp(CMGetCharsPtr(v1->string, NULL), + CMGetCharsPtr(v2->string, NULL)) == 0); + case CMPI_dateTime: + CHECK_PTRS; + return (strcmp(CMGetCharsPtr( + CMGetStringFormat(v1->dateTime, NULL), NULL), + CMGetCharsPtr( + CMGetStringFormat(v2->dateTime, NULL), NULL)) == 0); + default: + return true; + } + return true; +} + +/* + * Compare 2 instances. + * They can be same, different in key properties or different in non-key props + */ +IMIDiff compare_instances(CMPIInstance *i1, CMPIInstance *i2) +{ + CMPIString *prop_name = NULL; + // At first compare key properties. They will differ in most cases + // Then compare non-key properties + CMPIObjectPath *op1 = CMGetObjectPath(i1, NULL); + CMPIObjectPath *op2 = CMGetObjectPath(i2, NULL); + int pcount1 = CMGetKeyCount(op1, NULL); + int pcount2 = CMGetKeyCount(op2, NULL); + + if (pcount1 != pcount2) { + // They differ in count of key properties +// DEBUG("compare instances: Different count of properties"); + return IM_I_DIFFERENT; + } + int i; + CMPIData data1, data2; + // Check key properties one by one + for (i = 0; i < pcount1; i++) { + data1 = CMGetKeyAt(op1, i, &prop_name, NULL); + data2 = CMGetKey(op2, CMGetCharsPtr(prop_name, NULL), NULL); + // XXX - is state really CMPI_badValue? + if (data2.state == CMPI_badValue || data1.type != data2.type || + !compare_values(&data1.value, &data2.value, data1.type)) { + return IM_I_DIFFERENT; + } + } + // Check all properties one by one + pcount1 = CMGetPropertyCount(i1, NULL); + pcount2 = CMGetPropertyCount(i2, NULL); + if (pcount1 != pcount2) { + return IM_I_DIFFERENT; + } + for (i = 0; i < pcount1; i++) { + data1 = CMGetPropertyAt(i1, i, &prop_name, NULL); + data2 = CMGetProperty(i2, CMGetCharsPtr(prop_name, NULL), NULL); + // XXX - is state really CMPI_badValue? + if (data2.state == CMPI_badValue || data1.type != data2.type) { + return IM_I_DIFFERENT; + } else if (!compare_values(&data1.value, &data2.value, data1.type)) { + return IM_I_CHANGE; + } + } + return IM_I_SAME; +} + + +/* + * Remove (free()) the first polled diff from diff list + * returns nothing + * Does not perform any checks + */ +void remove_diff(IMPolledDiffs *d) +{ + IMPolledDiff *first = d->first; + if (first) { + d->first = first->next; + free (first); + } +} + +/* + * add instance pair to the list of diffs + * First argument is diffs list. + * Returns true when ok, false if not and set IMError + */ +bool add_diff(IMPolledDiffs *d, CMPIInstance *newi, CMPIInstance *oldi, + IMError *err) +{ + IMPolledDiff *nd = malloc(sizeof(IMPolledDiff)); + if (!nd) { + *err = IM_ERR_MALLOC; + return false; + } + nd->inew = newi; + nd->iold = oldi; + nd->next = NULL; + if (!d->first) { + d->first = nd; + } else { + IMPolledDiff *last = d->first; + while (last->next) last = last->next; + last->next = nd; + } + return true; +} + +/* + * Generate diffs based on polled enumerations. + * Generated diff will contain: + * - pair => there is a change + * - old NULL and new value => new instance created + * - old value and new NULL => instance deletion + * Return true when ok, false if not and set IMError + */ +bool gen_diffs(IMManager *manager, IMPolledDiffs *d, IMError *err) +{ + DEBUG("gen diffs called"); + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!manager->enums) { + return false; + } + IMEnumerationPair *pair = manager->enums->first; + while (pair) { +// DEBUG("gen diffs - next pair"); + CMPIArray *penum = pair->prev_enum; + CMPIArray *tenum = pair->this_enum; + if (penum && tenum) { + int pcount = CMGetArrayCount(penum, NULL); + int tcount = CMGetArrayCount(tenum, NULL); + bool p_used[pcount], t_used[tcount]; + bzero(p_used, pcount); + bzero(t_used, tcount); + int pi = 0, ti = 0; + CMPIInstance *newi = NULL; + CMPIInstance *oldi = NULL; + bool do_compare = true; + for (pi = 0; pi < pcount; pi++) { + oldi = CMGetArrayElementAt(penum, pi, NULL).value.inst; + do_compare = true; + for (ti = 0; ti < tcount && do_compare; ti++) { + if (t_used[ti] == true) continue; + newi = CMGetArrayElementAt(tenum, ti, NULL).value.inst; // XXX are them really in same order, on the same index? + switch (compare_instances(oldi, newi)) { + case IM_I_SAME: + DEBUG("gen diffs - same instances"); + do_compare = false; + t_used[ti] = true; + p_used[pi] = true; + break; + case IM_I_CHANGE: + DEBUG("gen diffs - change in instances"); + if (manager->type == IM_IND_MODIFICATION) { + if (!add_diff(d, newi, oldi, err)) { + return false; + } + } + do_compare = false; + t_used[ti] = true; + p_used[pi] = true; + break; + case IM_I_DIFFERENT: + DEBUG("gen diffs - different instances"); + default: break; + } + } + } + if (manager->type == IM_IND_DELETION) { + for (pi = 0; pi < pcount; pi++) { + if (p_used[pi] == false) { + DEBUG("gen diffs - deleted instance"); + if (!add_diff(d, NULL, + CMGetArrayElementAt(penum, pi, NULL).value.inst, + err)) { + return false; + } + } + } + } + if (manager->type == IM_IND_CREATION) { + for (ti = 0; ti < tcount; ti++) { + if (t_used[ti] == false) { + DEBUG("gen diffs - created instance"); + if (!add_diff(d, + CMGetArrayElementAt(tenum, ti, NULL).value.inst, + NULL, err)) { + return false; + } + } + } + } + + } + pair = pair->next; + } + DEBUG("gen diffs returning true"); + return true; +} + + +/* helper functions */ + +/* + * Extract object path from select expression + * returns match for the first occurence of ISA operator + */ +CMPIObjectPath *get_object_path(CMPIBroker *broker, CMPISelectExp *se) +{ + CMPISelectCond *sc = CMGetDoc(se, NULL); + CMPICount scount = CMGetSubCondCountAndType(sc,NULL,NULL); + unsigned int i, j; + for (i = 0; i < scount; i++) { + CMPISubCond *subcond = CMGetSubCondAt(sc,i,NULL); + CMPICount pcount = CMGetPredicateCount(subcond,NULL); + for (j = 0; j < pcount; j++) { + CMPIPredicate *pred = CMGetPredicateAt(subcond,j,NULL); + CMPIType type; + CMPIPredOp op; + CMPIString *lhs=NULL; + CMPIString *rhs=NULL; + CMGetPredicateData(pred,&type,&op,&lhs,&rhs); + if (op == CMPI_PredOp_Isa) { + return CMNewObjectPath(broker, "root/cimv2", + CMGetCharsPtr(rhs, NULL), NULL); + } + } + } + return NULL; +} + +// compare 2 object paths, return true when equal, false if not +// equality is set by comparing class names and namespaces +bool equal_ops(CMPIObjectPath *op1, CMPIObjectPath *op2) +{ + return (strcmp(NS_STR_FROM_OP(op1), NS_STR_FROM_OP(op2)) == 0 && + strcmp(CN_STR_FROM_OP(op1), CN_STR_FROM_OP(op2)) == 0); +} + +// Try to find enumeration with given object path +// if found increase reference count +// if not found add it to the end of list with ref count = 1 +bool add_enumeration(IMManager *manager, CMPIObjectPath *op, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!op) { + *err = IM_ERR_OP; + return false; + } + if (!manager->enums) { + if (!(manager->enums = malloc(sizeof(IMEnumerations)))) { + *err = IM_ERR_MALLOC; + return false; + } + manager->enums->first = NULL; + } + IMEnumerationPair *e = manager->enums->first; + IMEnumerationPair *last = NULL; + while (e) { // find last enumeration + if (compare_objectpaths(op, e->op)) { + e->ref_count++; + return true; + } + last = e; + e = e->next; + } + IMEnumerationPair *new_ime = malloc(sizeof(IMEnumerationPair)); + if (!new_ime) { + *err = IM_ERR_MALLOC; + return false; + } + new_ime->next = NULL; + new_ime->op = CMClone(op, NULL); + new_ime->ref_count = 1; + new_ime->prev_enum = NULL; + new_ime->this_enum = NULL; + if (!last) { // there isn't any enumeration yet + manager->enums->first = new_ime; + } else { + last->next = new_ime; + } + return true; +} + +// Pair enumerations. This enum will become prev enum and new this enum +// will be created by new obtained by object path +bool pair_enumeration(IMManager *manager, IMEnumerationPair *epair) +{ + CMPIEnumeration *aux_e = CBEnumInstances(manager->broker, + manager->ctx_manage, epair->op, NULL, NULL); + if (!aux_e) { + return false; + } + CMPIArray *new_e = CMClone(CMToArray(aux_e, NULL), NULL); + if (!epair->prev_enum && !epair->this_enum) { + epair->prev_enum = CMClone(new_e, NULL); + epair->this_enum = new_e; + return true; + } + if (epair->prev_enum) { + CMRelease(epair->prev_enum); + } + epair->prev_enum = epair->this_enum; + epair->this_enum = new_e; + return true; +} + +bool _im_poll(IMManager *manager, IMError *err) +{ + DEBUG("IM poll called"); + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!manager->enums) { + // Nothing to poll + DEBUG("IM poll nothing to poll"); + return true; + } + IMEnumerationPair *epair = NULL; + // go thru all enumerations' object path + // Use object path to enumerate instances + DEBUG("IM poll will go thru all filters"); + for (epair = manager->enums->first; epair; epair = epair->next) { + DEBUG("IM poll next enumeration"); + pair_enumeration(manager, epair); + } + DEBUG("IM poll returning true"); + return true; +} + +/* + * Fill ind_inst with properties and send it + */ +bool send_creation_indication(CMPIInstance *ind_inst, CMPIInstance *new_inst, + IMManager *manager) +{ + DEBUG("Instance creation indication to be send"); + if (!manager || !ind_inst || !new_inst) { + return false; + } + CMPIObjectPath *ind_op = CMGetObjectPath(ind_inst, NULL); + const char *ns = CMGetCharsPtr(CMGetNameSpace(ind_op, NULL), NULL); + CMSetProperty(ind_inst, "SourceInstance", &new_inst, CMPI_instance); + CBDeliverIndication(manager->broker, manager->ctx_manage, ns, ind_inst); + return true; +} + +/* + * Fill ind_inst with properties and send it + */ +bool send_deletion_indication(CMPIInstance *ind_inst, CMPIInstance *old_inst, + IMManager *manager) +{ + DEBUG("Instance deletion indication to be send"); + if (!manager || !ind_inst || !old_inst) { + return false; + } + CMPIObjectPath *ind_op = CMGetObjectPath(ind_inst, NULL); + const char *ns = CMGetCharsPtr(CMGetNameSpace(ind_op, NULL), NULL); + CMSetProperty(ind_inst, "SourceInstance", &old_inst, CMPI_instance); + CBDeliverIndication(manager->broker, manager->ctx_manage, ns, ind_inst); + return true; +} + +/* + * Fill ind_inst with properties and send it + */ +bool send_modification_indication(CMPIInstance *ind_inst, CMPIInstance *new, + CMPIInstance *old, IMManager *manager) +{ + DEBUG("Instance modification indication to be send"); + if (!manager || !ind_inst || !new || !old) { + return false; + } + CMPIObjectPath *ind_op = CMGetObjectPath(ind_inst, NULL); + const char *ns = CMGetCharsPtr(CMGetNameSpace(ind_op, NULL), NULL); + CMSetProperty(ind_inst, "SourceInstance", &new, CMPI_instance); + CMSetProperty(ind_inst, "PreviousInstance", &old, CMPI_instance); + CBDeliverIndication(manager->broker, manager->ctx_manage, ns, ind_inst); + return true; +} + +/* + * Will extract class name from select expression + * example: SELECT * FROM LMI_AccountInstCreationIndication WHERE ... + * will return LMI_AccountInstCreationIndication + * returns NULL when not found or on error + * Caller has to free memory. + */ +char *get_classname(CMPISelectExp *se) +{ + if (!se) { + return NULL; + } +// TODO remove when working +#if 0 + char *select = (char*)CMGetCharsPtr(CMGetSelExpString(se, NULL), NULL); + char *after_from = strcasestr(select, "from") + strlen("from"); + char *start = after_from + strspn(after_from, " "); + char *end = index(start,' '); + char *cname = malloc(end-start+1); + bzero(cname, end-start+1); + return memcpy(cname, start, end-start); +#else + char *select = (char*)CMGetCharsPtr(CMGetSelExpString(se, NULL), NULL); + char *rest = NULL, *token = NULL, *ptr = select; + for (; ptr || strcasecmp(token, "from") != 0; ptr = NULL) { + token = strtok_r(ptr, " ", &rest); + if (!token) { + break; + } + } + if (token) { + token = strtok_r(ptr, " ", &rest); + return strdup(token); + } + return NULL; +#endif +} + +/* + * Main send indication function. Will decide by type of indication what + * to send. + * Returns true when everything is OK. False otherwise. + */ +bool send_indication(CMPIInstance *old, CMPIInstance *new, IMManager *manager) +{ + if (!manager) { + return false; + } + + CMPIObjectPath *op = CMNewObjectPath( + manager->broker, + CMGetCharsPtr(CMGetNameSpace(manager->enums->first->op, NULL), NULL), + manager->filters->class_name, + NULL); + DEBUG("Will send indication with this OP: %s", + CMGetCharsPtr(CDToString(manager->broker, op, NULL), NULL)); + CMPIInstance *ind_inst = CMNewInstance(manager->broker, op, NULL); + DEBUG("Will send indication with this instance): %s", + CMGetCharsPtr(CDToString(manager->broker, ind_inst, NULL), NULL), NULL); + DEBUG("Old instance: %p - %s", old, + CMGetCharsPtr(CDToString(manager->broker, old, NULL), NULL), NULL); + DEBUG("New instance: %p - %s", new, + CMGetCharsPtr(CDToString(manager->broker, new, NULL), NULL), NULL); + + CMPIDateTime *date = CMNewDateTimeFromBinary(manager->broker, + time(NULL) * 1000000, + 0, NULL); + CMSetProperty(ind_inst, "IndicationTime", &date, CMPI_dateTime); + + switch (manager->type) { + case IM_IND_CREATION: + if (!new) { + return false; + } + send_creation_indication(ind_inst, new, manager); + break; + case IM_IND_DELETION: + if (!old) { + return false; + } + send_deletion_indication(ind_inst, old, manager); + break; + case IM_IND_MODIFICATION: + if (!old || !new) { + return false; + } + send_modification_indication(ind_inst, new, old, manager); + break; + } + CMRelease(date); + return true; +} + +/* + * Run in separate thread + */ +static void *manage(void *data) +{ + //TODO - make this thread's cancel state and type more specific? + // Set the type to PTHREAD_CANCEL_ASYNCHRONOUS? + // Switch Enable/Disable state on some places? + IMManager *manager = (IMManager *)data; + IMError err = IM_ERR_OK; // TODO - How to handle potential errors? + DEBUG("manage thread started"); + + IMPolledDiffs diffs = {NULL}; + CMPIInstance *iold = NULL, *inew = NULL; + + DEBUG("manage thread - attaching thread manager context = %p", manager->ctx_manage); + CBAttachThread(manager->broker, manager->ctx_manage); + + while (1) { + DEBUG("manage thread in infinite loop"); + // wait until manager is running + pthread_mutex_lock(&_t_mutex); + while (!manager->running) { + DEBUG("manage thread waiting to indication start"); + pthread_cond_wait(&_t_cond, &_t_mutex); + } + pthread_mutex_unlock(&_t_mutex); + + DEBUG("manage thread calling watcher"); + if(!manager->watcher(manager->data)) { + continue; + } + pthread_mutex_lock(&_t_mutex); + if (manager->polling) { + // poll enumerations + if (!_im_poll(manager, &err)) { + pthread_mutex_unlock(&_t_mutex); + return (void *)err; + } + // create list of diffs + if (!gen_diffs(manager, &diffs, &err)) { + pthread_mutex_unlock(&_t_mutex); + return (void *)err; + } + manager->data = &diffs; + } + DEBUG("manage thread calling gather"); + while (manager->gather(&iold, &inew, manager->data)) { + send_indication(iold, inew, manager); + } + pthread_mutex_unlock(&_t_mutex); + } +} + +/* + * Default gather function using polling + * It is going thru all polled instances. If there is some pair (previous enum + * and this enum) + */ +static bool default_gather (CMPIInstance **old_inst, CMPIInstance **new_inst, + void* data) +{ + // passed data is a list of diffs + // It is going to be called in loop + // return true if now we are going to create indication + // return false if there will be no indication and loop will break + IMPolledDiffs *diffs = (IMPolledDiffs *) data; + if (diffs->first) { + *new_inst = diffs->first->inew; + *old_inst = diffs->first->iold; + remove_diff(diffs); + return true; + } + return false; +} + +/* + * Remove every polled enums + * true if ok, false if not + */ +static bool remove_all_enums(IMManager *manager, IMError *err) +{ + if (!manager->enums) { + return true; + } + IMEnumerationPair *current = manager->enums->first; + IMEnumerationPair *next = NULL; + while (current) { + next = current->next; + CMRelease(current->prev_enum); + CMRelease(current->this_enum); + CMRelease(current->op); + free(current); + current = next; + } + free(manager->enums); + manager->enums = NULL; + return true; +} + +/* + * true if ok + * false if not + */ +static bool remove_all_filters(IMManager *manager, IMError *err) +{ + if (!manager->filters) { + return true; + } + IMFilter *current = manager->filters->first; + IMFilter *next = NULL; + while (current) { + next = current->next; + free(current->select_exp_string); + CMRelease(current->op); + free(current); + current = next; + } + free(manager->filters->class_name); + free(manager->filters); + manager->filters = NULL; + + if (!remove_all_enums(manager, err)) { + return false; + } + + return true; +} + +IMManager* im_create_manager(IMInstGather gather, IMFilterChecker f_checker, + bool polling, IMEventWatcher watcher, + IMIndType type, CMPIBroker *broker, IMError *err) +{ + if ((!polling && !gather) || (polling && gather) ) { + *err = IM_ERR_GATHER; + return NULL; + } + if (!f_checker) { + *err = IM_ERR_FILTER_CHECKER; + return NULL; + } + if (!watcher) { + *err = IM_ERR_WATCHER; + return NULL; + } + if (!broker) { + *err = IM_ERR_BROKER; + return NULL; + } + IMManager* manager = malloc(sizeof(IMManager)); + if (!manager) { + *err = IM_ERR_MALLOC; + return NULL; + } + manager->type = type; + manager->gather = gather ? gather : default_gather; + manager->watcher = watcher; + manager->filters = NULL; + manager->running = false; + manager->polling = polling; + manager->broker = broker; + manager->f_checker = f_checker; + manager->enums = NULL; + manager->ctx_main = NULL; + manager->ctx_manage = NULL; + DEBUG("Manager created"); + + if (pthread_mutex_init(&_t_mutex, NULL) || + pthread_cond_init(&_t_cond, NULL)) { + *err = IM_ERR_THREAD; + free(manager); + return NULL; + } + + return manager; +} + +bool im_destroy_manager(IMManager *manager, const CMPIContext *ctx, + IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + pthread_mutex_lock(&_t_mutex); + manager->ctx_main = ctx; + if (!im_stop_ind(manager, ctx, err)) { + pthread_mutex_unlock(&_t_mutex); + return false; + } + if (!remove_all_filters(manager, err)) { + pthread_mutex_unlock(&_t_mutex); + return false; + } + if (pthread_cancel(_t_manage)) { + *err = IM_ERR_THREAD; + pthread_mutex_unlock(&_t_mutex); + return false; + } + + pthread_mutex_unlock(&_t_mutex); + DEBUG("Destroying manager"); + if (pthread_mutex_destroy(&_t_mutex)) { + *err = IM_ERR_THREAD; + return false; + } + free(manager); + return true; +} + +bool im_verify_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return NULL; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + } + if (!filter) { + *err = IM_ERR_FILTER; + return NULL; + } + manager->ctx_main = ctx; + DEBUG("Verifying filter. Manager = %p, filter checker = %p", manager, manager->f_checker); + return manager->f_checker(filter); +} + +/* + * Because Pegasus cannot easily clone nor copy select expression + * we need to dig internals and store them directly. + * Return true when ok, or NULL and error set + * appropiately + */ +bool _im_add_filter(IMManager *manager, CMPISelectExp *se, IMError *err) +{ + if (!se) { + *err = IM_ERR_SELECT_EXP; + return false; + } + + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + + CMPIString *str = CMGetSelExpString(se, &im_rc); + if (!str) { + *err = IM_ERR_CMPI_RC; + return false; + } + + const char *query = CMGetCharsPtr(str, &im_rc); + if (!query) { + *err = IM_ERR_CMPI_RC; + return false; + } + char *ses = NULL; + if (!(ses = strdup(query))) { + *err = IM_ERR_MALLOC; + return false; + } + + if(!manager->filters) { + DEBUG("This is the first filter creation"); + IMFilters *filters = malloc(sizeof(IMFilters)); + if (!filters) { + *err = IM_ERR_MALLOC; + free(ses); + return false; + } + if (!(filters->class_name = get_classname(se))) { + *err = IM_ERR_MALLOC; + free(ses); + free(filters); + return false; + } + filters->first = NULL; + manager->filters = filters; + } + + DEBUG("Adding filter to the list"); + IMFilter *current = manager->filters->first; + IMFilter *prev = NULL; + while (current) { + prev = current; + current = current->next; + } + IMFilter *next = malloc(sizeof(IMFilter)); + if (!next) { + *err = IM_ERR_MALLOC; + free(ses); + return false; + } + next->next = NULL; + next->select_exp_string = ses; + next->op = CMClone(get_object_path(manager->broker, se), NULL); + if (prev) { + prev->next = next; + } else { + manager->filters->first = next; + } + + return true; +} + +bool im_add_filter(IMManager *manager, CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err) +{ + pthread_mutex_lock(&_t_mutex); + if (!manager) { + *err = IM_ERR_MANAGER; + pthread_mutex_unlock(&_t_mutex); + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + pthread_mutex_unlock(&_t_mutex); + return false; + } + if (!filter) { + *err = IM_ERR_FILTER; + pthread_mutex_unlock(&_t_mutex); + return false; + } + manager->ctx_main = ctx; + DEBUG("Adding filter"); + if (!_im_add_filter(manager, filter, err)) { + pthread_mutex_unlock(&_t_mutex); + return false; + } + if (manager->polling) { + if (!add_enumeration(manager, + get_object_path(manager->broker, filter), + err)) { + pthread_mutex_unlock(&_t_mutex); + return false; + } + } + pthread_mutex_unlock(&_t_mutex); + return true; +} + +/* + * Decrease reference count for polled enumerations. If count is 0, remove + * the whole enumerations. + * Returns true/false on success/fail and set err. + */ +bool maybe_remove_polled(IMManager *manager, CMPIObjectPath *op, IMError *err) +{ + if (!manager->enums || !manager->enums->first) { + *err = IM_ERR_NOT_FOUND; + return false; + } + IMEnumerationPair *current = manager->enums->first; + IMEnumerationPair *prev = NULL; + while (current) { + if (compare_objectpaths(op, current->op)) { + if (--current->ref_count == 0) { + if (prev) { + prev->next = current->next; + } else { + manager->enums->first = current->next; + } + CMRelease(current->prev_enum); + CMRelease(current->this_enum); + CMRelease(current->op); + } + return true; + } + prev = current; + current = current->next; + } + *err = IM_ERR_NOT_FOUND; + return false; +} + +bool im_remove_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + if (!filter) { + *err = IM_ERR_FILTER; + return false; + } + pthread_mutex_lock(&_t_mutex); + if (!manager->filters) { + pthread_mutex_unlock(&_t_mutex); + *err = IM_ERR_NOT_FOUND; + return false; + } + manager->ctx_main = ctx; + DEBUG("Removing filter"); + IMFilter *current = manager->filters->first; + IMFilter *prev = NULL; + while (current) { + if (strcmp(current->select_exp_string, + CMGetCharsPtr(CMGetSelExpString(filter, NULL), NULL)) == 0) { + if (prev) { + prev->next = current->next; + } else { + manager->filters->first = current->next; + } + if (manager->polling && !maybe_remove_polled(manager, current->op, + err)) { + pthread_mutex_unlock(&_t_mutex); + return false; + } + CMRelease(current->op); + free(current->select_exp_string); + free(current); + pthread_mutex_unlock(&_t_mutex); + return true; + } + prev = current; + current = current->next; + } + *err = IM_ERR_NOT_FOUND; + pthread_mutex_unlock(&_t_mutex); + return false; +} + +// start indications +bool im_start_ind(IMManager *manager, const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + manager->ctx_main = ctx; + manager->ctx_manage = CBPrepareAttachThread(manager->broker, + manager->ctx_main); + DEBUG("Creating second thread"); + if (pthread_create(&_t_manage, NULL, manage, manager)) { + *err = IM_ERR_THREAD; + return false; + } + DEBUG("Starting indications"); + manager->running = true; + pthread_cond_signal(&_t_cond); + return true; +} + +// stop indications +bool im_stop_ind(IMManager *manager, const CMPIContext *ctx, IMError *err) +{ + if (!manager) { + *err = IM_ERR_MANAGER; + return false; + } + if (!ctx) { + *err = IM_ERR_CONTEXT; + return false; + } + manager->ctx_main = ctx; + DEBUG("Stopping indications"); + manager->running = false; + return true; +} + diff --git a/src/indmanager/ind_manager.h b/src/indmanager/ind_manager.h new file mode 100644 index 0000000..2768925 --- /dev/null +++ b/src/indmanager/ind_manager.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2013 Red Hat, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Roman Rakus <rrakus@redhat.com> + */ + +/* + * Limitations: + * * Queries * + * - supports only DMTF:CQL query language + * - select expression has to have ISA predicatea + * + * * Indication types * + * - supports only CIM_InstCreation, CIM_InstDeletion, CIM_InstModification + */ + +#include <cmpi/cmpidt.h> +#include <cmpi/cmpift.h> +#include <cmpi/cmpimacs.h> + +#include <stdbool.h> + +/* + * Usage: + * 1) Define the following callback functions: + * IMInstGather + * Used only when not using polling + * In this function you should set old and/or new pointers to CMPIInstance. + * These instances will be send as part of indication. + * The table bellow show which pointers have to be set and are used + * indication type old instance new instance + * -------------------------------------------------------- + * IM_IND_CREATION NULL instance w/ new values + * IM_IND_DELETION instance w/ old values NULL + * IM_IND_MODIFICATION instance w/ old values instance w/ new values + * + * You can use data (see below) pointer to pass any data + * + * This function is reentrant. When you return true, the function is called + * again. Return false, when you don't want to be called again. + * + * IMFilterChecker + * In this function implement checking of filter. Passed filter should be + * checked. Return true when filter is allowed, false otherwise. + * + * IMEventWatcher + * This is supposed to be blocking function. Returning the true will inform + * the indication manager that the event has occured. You can set data for + * further processing in IMInstGather. + * + * 2) Create manager object using im_create_manager() during provider init + * 3) Call other functions as necessary + * im_destroy_manager() in provider cleanup + * im_verify_filter() in provider's authorize filter (also in activate filter) + * im_add_filter() in provider's activate filter + * im_remove_filter() in provider's deactivate filter + * im_start_ind() in provider's enable indications + * im_stop_ind() in provider's disable indications + */ + +// callback functions +typedef bool (*IMInstGather) (CMPIInstance **old, CMPIInstance **new, void *data); +typedef bool (*IMFilterChecker) (const CMPISelectExp *filter); +typedef bool (*IMEventWatcher) (void *data); + +// Indication types +// Only one type supported per instance of manager +typedef enum { + // for instance indications + IM_IND_CREATION, // instance creation + IM_IND_DELETION, // instance deletion + IM_IND_MODIFICATION, // instance modification +} IMIndType; + + +/* + * Definition of IMFilter + * Linked list of all filters + */ +typedef struct _IMFilter { + struct _IMFilter *next; + char *select_exp_string; + CMPIObjectPath *op; +} IMFilter; + +typedef struct _IMFilters { + IMFilter *first; + char *class_name; +} IMFilters; + +/* + * Linked list of polled enumeration pairs of instances + * enumerations are converted to arrays + */ +typedef struct _IMEnumerationPair { + struct _IMEnumerationPair *next; + CMPIObjectPath *op; + CMPIArray *prev_enum; + CMPIArray *this_enum; + unsigned ref_count; +} IMEnumerationPair; + +typedef struct _IMEnumerations { + IMEnumerationPair *first; +} IMEnumerations; + +typedef struct _IMManager { + // callback functions + IMEventWatcher watcher; + IMInstGather gather; + IMFilterChecker f_checker; + // filters container + IMFilters *filters; + // others + IMIndType type; + bool running; + bool polling; + CMPIBroker *broker; + const CMPIContext *ctx_main; /* main thread */ + CMPIContext *ctx_manage; /* manage thread */ + IMEnumerations *enums; + // passed data, used for communication between gather/watcher/etc. + void *data; +} IMManager; + +typedef enum { + IM_ERR_OK, + IM_ERR_GATHER, // bad or null gather callback + IM_ERR_FILTER_CHECKER, // bad or null filter checker callback + IM_ERR_WATCHER, // bad or null watcher callback + IM_ERR_MALLOC, // memory allocation error + IM_ERR_FILTER, // bad or null filter + IM_ERR_MANAGER, // bad or null manager + IM_ERR_BROKER, // bad or null broker + IM_ERR_CONTEXT, // bad or null context + IM_ERR_SELECT_EXP, // bad or null select expression + IM_ERR_NOT_FOUND, // specified data were not found + IM_ERR_THREAD, // some error on threading + IM_ERR_CMPI_RC, // CMPI status not OK + IM_ERR_OP, // bad or null object path +} IMError; + +// Create manager with given properties and callbacks. +// gather may not be specified if you want to use polling +// Return newly created IMManager or NULL and appropiate IMError is set +IMManager* im_create_manager(IMInstGather gather, IMFilterChecker f_checker, + bool polling, IMEventWatcher watcher, + IMIndType type, CMPIBroker *broker, IMError *err); + +// Destroy given manager. +// Return true when ok or false and IMError is set +bool im_destroy_manager(IMManager *manager, const CMPIContext *ctx, + IMError *err); + +// Call verification callback on given manager and filter. +// Return true if filter is verified. False if not. +// IMError will be set to to other value than IM_ERR_OK +// if verification failed +bool im_verify_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err); + +// add given filter to manager. +// Return true when all is ok, otherwise false and IMError +// is as appropriate. +bool im_add_filter(IMManager *manager, CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err); + +// Remove given filter from manager. +// Return true when removed, false if not and appropriate IMError is set. +bool im_remove_filter(IMManager *manager, const CMPISelectExp *filter, + const CMPIContext *ctx, IMError *err); + +// Start indications. +// Return true when correctly started, false if not and +// appropriate IMError is set, +bool im_start_ind(IMManager *manager, const CMPIContext *ctx, IMError *err); + +// Stop indications. +// Return true when correctly stopped, false if not and +// appropriate IMError is set, +bool im_stop_ind(IMManager *manager, const CMPIContext *ctx, IMError *err); + diff --git a/src/indmanager/openlmiindmanager.pc.in b/src/indmanager/openlmiindmanager.pc.in new file mode 100644 index 0000000..b4d76b5 --- /dev/null +++ b/src/indmanager/openlmiindmanager.pc.in @@ -0,0 +1,10 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=${prefix}/include +libdir=${exec_prefix}/lib@LIB_SUFFIX@ + +Name: openlmiindmanager +Description: OpenLMI indication manager support +Version: @OPENLMIINDMANAGER_VERSION@ +Libs: -L${libdir} -lopenlmiindmanager +CFlags: -I${includedir}/openlmi |