Hi All,
Good news, libiscsi is almost done (only ibft discovery is left to do) and has
been reviewed on the open iscsi mailinglist. I've got a go ahead from mchristie
to add libiscsi to Fedora's iscsi-initiator-utils packages, so it will show up
there soon (when ibft is done).
I've also been working on libiscsi python bindings. Since I'm new to this a
review of this would be really good IMHO. Attached are the current libiscsi.h
file and my current pythonbindings (also finished except for ibft).
I've tested it and it works well for me.
Thanks & Regards,
Hans
#include <netdb.h>
/** \brief Maximum length for iSCSI values.
*
* Maximum length for iSCSI values such as hostnames and parameter values.
*/
#define LIBISCSI_VALUE_MAXLEN 256
/** \brief supported authentication methods
*
* This enum lists all supported authentication methods.
*/
enum libiscsi_auth_t {
libiscsi_auth_none /** No authentication */,
libiscsi_auth_chap /** CHAP authentication */,
};
/** \brief libiscsi context struct
*
* Note: even though libiscsi uses a context struct, the underlying open-iscsi
* code does not, so libiscsi is not thread safe, not even when using one
* context per thread!
*/
struct libiscsi_context;
/** \brief iSCSI node record
*
* Struct holding data uniquely identifying an iSCSI node.
*/
struct libiscsi_node {
char name[LIBISCSI_VALUE_MAXLEN] /** iSCSI iqn for the node. */;
int tpgt /** Portal group number. */;
/* Note open-iscsi has some code in place for multiple connections in one
node record and thus multiple address / port combi's, but this does not
get used anywhere, so we keep things simple and assume one connection */
char address[NI_MAXHOST] /** Portal hostname or IP-address. */;
int port /** Portal port number. */;
};
/** \brief libiscsi CHAP authentication information struct
*
* Struct holding all data needed for CHAP login / authentication. Note that
* \e reverse_username may be a 0 length string in which case only forward
* authentication will be done.
*/
struct libiscsi_chap_auth_info {
char username[LIBISCSI_VALUE_MAXLEN] /** Username */;
char password[LIBISCSI_VALUE_MAXLEN] /** Password */;
char reverse_username[LIBISCSI_VALUE_MAXLEN] /** Reverse Username */;
char reverse_password[LIBISCSI_VALUE_MAXLEN] /** Reverse Password */;
};
/** \brief generic libiscsi authentication information struct
*
* Struct holding authentication information for discovery and login.
*/
struct libiscsi_auth_info {
enum libiscsi_auth_t method /** Authentication method to use */;
union {
struct libiscsi_chap_auth_info chap /** Chap specific info */;
} /** Union holding method depenend info */;
};
/** \brief Initalize libiscsi
*
* This function creates a libiscsi context and initalizes it. This context
* is need to use other libiscsi funtions.
*
* \return A pointer to the created context, or NULL in case of an error.
*/
struct libiscsi_context *libiscsi_init(void);
/** \brief Cleanup libiscsi used resource
*
* This function cleanups any used resources and then destroys the passed
* context. After this the passed in context may no longer be used!
*
* \param context libiscsi context to operate on.
*/
void libiscsi_cleanup(struct libiscsi_context *context);
/** \brief Discover iSCSI nodes using sendtargets and add them to the node db.
*
* This function connects to the given address and port and then tries to
* discover iSCSI nodes using the sendtargets protocol. Any found nodes are
* added to the local iSCSI node database and are returned in a dynamically
* allocated array.
*
* Note that the (optional) authentication info is for authenticating the
* discovery, and is not for the found nodes! If the connection(s) to the
* node(s) need authentication too, you can set the username / password for
* those (which can be different!) using the libiscsi_node_set_auth() function.
*
* \param context libiscsi context to operate on.
* \param address Hostname or IP-address to connect to.
* \param port Port to connect to, or 0 for the default port.
* \param auth_info Authentication information, or NULL.
* \param nr_found The number of found nodes will be returned
* through this pointer if not NULL.
* \param found_nodes The address of the dynamically allocated array
* of found nodes will be returned through this
* pointer if not NULL. The caller must free this
* array using free().
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_discover_sendtargets(struct libiscsi_context *context,
const char *address, int port, const struct libiscsi_auth_info *auth_info,
int *nr_found, struct libiscsi_node **found_nodes);
/** \brief Read iSCSI node info from firmware and add them to the node db.
*
* This function discovers iSCSI nodes using firmware (ppc or ibft). Any found
* nodes are added to the local iSCSI node database and are returned in a
* dynamically allocated array.
*
* Note that unlike sendtargets discovery, this function will also read
* authentication info and store that in the database too.
*
* Note this function currently is a stub which will always return -EINVAL
* (IOW it is not yet implemented)
*
* \param context libiscsi context to operate on.
* \param nr_found The number of found nodes will be returned
* through this pointer if not NULL.
* \param found_nodes The address of the dynamically allocated array
* of found nodes will be returned through this
* pointer if not NULL. The caller must free this
* array using free().
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_discover_firmware(struct libiscsi_context *context,
int *nr_found, struct libiscsi_node **found_nodes);
/** \brief Check validity of the given authentication info.
*
* This function checks the validity of the given authentication info. For
* example in case of CHAP, if the username and password are not empty.
*
* This function is mainly intended for use by language bindings.
*
* \param context libiscsi context to operate on.
* \param auth_info Authentication information to check.
* \return 0 on success, otherwise EINVAL.
*/
int libiscsi_verify_auth_info(struct libiscsi_context *context,
const struct libiscsi_auth_info *auth_info);
/** \brief Set the authentication info for the given node.
*
* This function sets the authentication information for the node described by
* the given node record. This will overwrite any existing authentication
* information.
*
* This is the way to specify authentication information for nodes found
* through sendtargets discovery.
*
* Note:
* 1) This is a convience wrapper around libiscsi_node_set_parameter(),
* setting the node.session.auth.* parameters.
* 2) For nodes found through firmware discovery the authentication information
* has already been set from the firmware.
* 3) \e auth_info may be NULL in which case any existing authinfo will be
* cleared.
*
* \param context libiscsi context to operate on.
* \param node iSCSI node to set auth information of
* \param auth_info Authentication information, or NULL.
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_node_set_auth(struct libiscsi_context *context,
const struct libiscsi_node *node,
const struct libiscsi_auth_info *auth_info);
/** \brief Login to an iSCSI node.
*
* Login to the iSCSI node described by the given node record.
*
* \param context libiscsi context to operate on.
* \param node iSCSI node to login to.
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_node_login(struct libiscsi_context *context,
const struct libiscsi_node *node);
/** \brief Logout of an iSCSI node.
*
* Logout of the iSCSI node described by the given node record.
*
* \param context libiscsi context to operate on.
* \param node iSCSI node to logout from.
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_node_logout(struct libiscsi_context *context,
const struct libiscsi_node *node);
/** \brief Set an iSCSI parameter for the given node
*
* Set the given nodes iSCSI parameter named by \e parameter to value \e value.
*
* \param context libiscsi context to operate on.
* \param node iSCSI node to change a parameter from.
* \param parameter Name of the parameter to set.
* \param value Value to set the parameter too.
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_node_set_parameter(struct libiscsi_context *context,
const struct libiscsi_node *node,
const char *parameter, const char *value);
/** \brief Get the value of an iSCSI parameter for the given node
*
* Get the value of the given nodes iSCSI parameter named by \e parameter.
*
* \param context libiscsi context to operate on.
* \param node iSCSI node to change a parameter from.
* \param parameter Name of the parameter to get.
* \param value The retreived value is stored here, this buffer must be
* atleast LIBISCSI_VALUE_MAXLEN bytes large.
* \return 0 on success, otherwise a standard error code
* (from errno.h).
*/
int libiscsi_node_get_parameter(struct libiscsi_context *context,
const struct libiscsi_node *node, const char *parameter, char *value);
/** \brief Get human readable string describing the last libiscsi error.
*
* This function can be called to get a human readable error string when a
* libiscsi function has returned an error. This function uses a single buffer
* per context, thus the result is only valid as long as no other libiscsi
* calls are made on the same context after the failing function call.
*
* \param context libiscsi context to operate on.
*
* \return human readable string describing the last libiscsi error.
*/
const char *libiscsi_get_error_string(struct libiscsi_context *context);
#include <Python.h>
#include "libiscsi.h"
static struct libiscsi_context *context;
/****************************** helpers ***********************************/
static int check_string(const char *string)
{
if (strlen(string) >= LIBISCSI_VALUE_MAXLEN) {
PyErr_SetString(PyExc_ValueError, "string too long");
return -1;
}
return 0;
}
/********************** PyIscsiChapAuthInfo ***************************/
typedef struct {
PyObject_HEAD
struct libiscsi_auth_info info;
} PyIscsiChapAuthInfo;
static int PyIscsiChapAuthInfo_init(PyObject *self, PyObject *args,
PyObject *kwds)
{
int i;
PyIscsiChapAuthInfo *chap = (PyIscsiChapAuthInfo *)self;
char *kwlist[] = {"username", "password", "reverse_username",
"reverse_password", NULL};
const char *string[4] = { NULL, NULL, NULL, NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwds,
"ss|ss:chapAuthInfo.__init__",
kwlist, &string[0], &string[1],
&string[2], &string[3]))
return -1;
for (i = 0; i < 4; i++)
if (string[i] && check_string(string[i]))
return -1;
memset (&chap->info, 0, sizeof(chap->info));
chap->info.method = libiscsi_auth_chap;
strcpy(chap->info.chap.username, string[0]);
strcpy(chap->info.chap.password, string[1]);
if (string[2])
strcpy(chap->info.chap.reverse_username, string[2]);
if (string[3])
strcpy(chap->info.chap.reverse_password, string[3]);
if (libiscsi_verify_auth_info(context, &chap->info)) {
PyErr_SetString(PyExc_ValueError,
libiscsi_get_error_string(context));
return -1;
}
return 0;
}
static PyObject *PyIscsiChapAuthInfo_get(PyObject *self, void *data)
{
PyIscsiChapAuthInfo *chap = (PyIscsiChapAuthInfo *)self;
const char *attr = (const char *)data;
if (!strcmp(attr, "username")) {
return PyString_FromString(chap->info.chap.username);
} else if (!strcmp(attr, "password")) {
return PyString_FromString(chap->info.chap.password);
} else if (!strcmp(attr, "reverse_username")) {
return PyString_FromString(chap->info.chap.reverse_username);
} else if (!strcmp(attr, "reverse_password")) {
return PyString_FromString(chap->info.chap.reverse_password);
}
return NULL;
}
static int PyIscsiChapAuthInfo_set(PyObject *self, PyObject *value, void *data)
{
PyIscsiChapAuthInfo *chap = (PyIscsiChapAuthInfo *)self;
const char *attr = (const char *)data;
const char *str;
if (!PyArg_Parse(value, "s", &str) || check_string(str))
return -1;
if (!strcmp(attr, "username")) {
strcpy(chap->info.chap.username, str);
} else if (!strcmp(attr, "password")) {
strcpy(chap->info.chap.password, str);
} else if (!strcmp(attr, "reverse_username")) {
strcpy(chap->info.chap.reverse_username, str);
} else if (!strcmp(attr, "reverse_password")) {
strcpy(chap->info.chap.reverse_password, str);
}
return 0;
}
static int PyIscsiChapAuthInfo_compare(PyIscsiChapAuthInfo *self,
PyIscsiChapAuthInfo *other)
{
int r;
r = strcmp(self->info.chap.username, other->info.chap.username);
if (r)
return r;
r = strcmp(self->info.chap.password, other->info.chap.password);
if (r)
return r;
r = strcmp(self->info.chap.reverse_username,
other->info.chap.reverse_username);
if (r)
return r;
r = strcmp(self->info.chap.reverse_password,
other->info.chap.reverse_password);
return r;
}
static PyObject *PyIscsiChapAuthInfo_str(PyObject *self)
{
PyIscsiChapAuthInfo *chap = (PyIscsiChapAuthInfo *)self;
char s[1024], reverse[512] = "";
if (chap->info.chap.reverse_username[0])
snprintf(reverse, sizeof(reverse), ", %s:%s",
chap->info.chap.reverse_username,
chap->info.chap.reverse_password);
snprintf(s, sizeof(s), "%s:%s%s", chap->info.chap.username,
chap->info.chap.password, reverse);
return PyString_FromString(s);
}
static struct PyGetSetDef PyIscsiChapAuthInfo_getseters[] = {
{"username", (getter)PyIscsiChapAuthInfo_get,
(setter)PyIscsiChapAuthInfo_set,
"username", "username"},
{"password", (getter)PyIscsiChapAuthInfo_get,
(setter)PyIscsiChapAuthInfo_set,
"password", "password"},
{"reverse_username", (getter)PyIscsiChapAuthInfo_get,
(setter)PyIscsiChapAuthInfo_set,
"reverse_username", "reverse_username"},
{"reverse_password", (getter)PyIscsiChapAuthInfo_get,
(setter)PyIscsiChapAuthInfo_set,
"reverse_password", "reverse_password"},
{NULL}
};
PyTypeObject PyIscsiChapAuthInfo_Type = {
PyObject_HEAD_INIT(NULL)
.tp_name = "libiscsi.chapAuthInfo",
.tp_basicsize = sizeof (PyIscsiChapAuthInfo),
.tp_getset = PyIscsiChapAuthInfo_getseters,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE,
.tp_compare = (cmpfunc)PyIscsiChapAuthInfo_compare,
.tp_init = PyIscsiChapAuthInfo_init,
.tp_str = PyIscsiChapAuthInfo_str,
.tp_new = PyType_GenericNew,
.tp_doc = "iscsi chap authentication information.",
};
/***************************** PyIscsiNode ********************************/
typedef struct {
PyObject_HEAD
struct libiscsi_node node;
} PyIscsiNode;
static int PyIscsiNode_init(PyObject *self, PyObject *args, PyObject *kwds)
{
PyIscsiNode *node = (PyIscsiNode *)self;
char *kwlist[] = {"name", "tpgt", "address", "port", NULL};
const char *name = NULL, *address = NULL;
int tpgt = -1, port = 3260;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|isi:node.__init__",
kwlist, &name, &tpgt, &address, &port))
return -1;
if (address == NULL) {
PyErr_SetString(PyExc_ValueError, "address not set");
return -1;
}
if (check_string(name) || check_string(address))
return -1;
strcpy(node->node.name, name);
node->node.tpgt = tpgt;
strcpy(node->node.address, address);
node->node.port = port;
return 0;
}
static PyObject *PyIscsiNode_get(PyObject *self, void *data)
{
PyIscsiNode *node = (PyIscsiNode *)self;
const char *attr = (const char *)data;
if (!strcmp(attr, "name")) {
return PyString_FromString(node->node.name);
} else if (!strcmp(attr, "tpgt")) {
return PyInt_FromLong(node->node.tpgt);
} else if (!strcmp(attr, "address")) {
return PyString_FromString(node->node.address);
} else if (!strcmp(attr, "port")) {
return PyInt_FromLong(node->node.port);
}
return NULL;
}
static int PyIscsiNode_set(PyObject *self, PyObject *value, void *data)
{
PyIscsiNode *node = (PyIscsiNode *)self;
const char *attr = (const char *)data;
const char *str;
int i;
if (!strcmp(attr, "name")) {
if (!PyArg_Parse(value, "s", &str) || check_string(str))
return -1;
strcpy(node->node.name, str);
} else if (!strcmp(attr, "tpgt")) {
if (!PyArg_Parse(value, "i", &i))
return -1;
node->node.tpgt = i;
} else if (!strcmp(attr, "address")) {
if (!PyArg_Parse(value, "s", &str) || check_string(str))
return -1;
strcpy(node->node.address, str);
} else if (!strcmp(attr, "port")) {
if (!PyArg_Parse(value, "i", &i))
return -1;
node->node.port = i;
}
return 0;
}
static int PyIscsiNode_compare(PyIscsiNode *self, PyIscsiNode *other)
{
int res;
res = strcmp(self->node.name, other->node.name);
if (res)
return res;
if (self->node.tpgt < other->node.tpgt)
return -1;
if (self->node.tpgt > other->node.tpgt)
return -1;
res = strcmp(self->node.address, other->node.address);
if (res)
return res;
if (self->node.port < other->node.port)
return -1;
if (self->node.port > other->node.port)
return -1;
return 0;
}
static PyObject *PyIscsiNode_str(PyObject *self)
{
PyIscsiNode *node = (PyIscsiNode *)self;
char s[1024], tpgt[16] = "";
if (node->node.tpgt != -1)
sprintf(tpgt, ",%d", node->node.tpgt);
snprintf(s, sizeof(s), "%s:%d%s %s", node->node.address,
node->node.port, tpgt, node->node.name);
return PyString_FromString(s);
}
static PyObject *PyIscsiNode_setAuth(PyObject *self, PyObject *args,
PyObject *kwds)
{
char *kwlist[] = {"authinfo", NULL};
int err;
PyIscsiNode *node = (PyIscsiNode *)self;
PyObject *arg;
const struct libiscsi_auth_info *authinfo = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &arg))
return NULL;
if (arg == Py_None) {
authinfo = NULL;
} else if (PyObject_IsInstance(arg, (PyObject *)
&PyIscsiChapAuthInfo_Type)) {
PyIscsiChapAuthInfo *pyauthinfo = (PyIscsiChapAuthInfo *)arg;
authinfo = &pyauthinfo->info;
} else {
PyErr_SetString(PyExc_ValueError, "invalid authinfo type");
return NULL;
}
err = libiscsi_node_set_auth(context, &node->node, authinfo);
if (err) {
PyErr_SetString(PyExc_IOError,
libiscsi_get_error_string(context));
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *PyIscsiNode_login(PyObject *self)
{
int err;
PyIscsiNode *node = (PyIscsiNode *)self;
err = libiscsi_node_login(context, &node->node);
if (err) {
PyErr_SetString(PyExc_IOError,
libiscsi_get_error_string(context));
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *PyIscsiNode_logout(PyObject *self)
{
int err;
PyIscsiNode *node = (PyIscsiNode *)self;
err = libiscsi_node_logout(context, &node->node);
if (err) {
PyErr_SetString(PyExc_IOError,
libiscsi_get_error_string(context));
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *PyIscsiNode_setParameter(PyObject *self, PyObject *args,
PyObject *kwds)
{
int err;
char *kwlist[] = {"parameter", "value", NULL};
PyIscsiNode *node = (PyIscsiNode *)self;
const char *parameter, *value;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss", kwlist,
¶meter, &value))
return NULL;
if (check_string(parameter) || check_string(value))
return NULL;
err = libiscsi_node_set_parameter(context, &node->node, parameter,
value);
if (err) {
PyErr_SetString(PyExc_IOError,
libiscsi_get_error_string(context));
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *PyIscsiNode_getParameter(PyObject *self, PyObject *args,
PyObject *kwds)
{
int err;
char *kwlist[] = {"parameter", NULL};
PyIscsiNode *node = (PyIscsiNode *)self;
const char *parameter;
char value[LIBISCSI_VALUE_MAXLEN];
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwlist, ¶meter))
return NULL;
if (check_string(parameter))
return NULL;
err = libiscsi_node_get_parameter(context, &node->node, parameter,
value);
if (err) {
PyErr_SetString(PyExc_IOError,
libiscsi_get_error_string(context));
return NULL;
}
return Py_BuildValue("s", value);
}
static struct PyGetSetDef PyIscsiNode_getseters[] = {
{"name", (getter)PyIscsiNode_get, (setter)PyIscsiNode_set,
"name", "name"},
{"tpgt", (getter)PyIscsiNode_get, (setter)PyIscsiNode_set,
"tpgt", "tpgt"},
{"address", (getter)PyIscsiNode_get, (setter)PyIscsiNode_set,
"address", "address"},
{"port", (getter)PyIscsiNode_get, (setter)PyIscsiNode_set,
"port", "port"},
{NULL}
};
static struct PyMethodDef PyIscsiNode_methods[] = {
{"setAuth", (PyCFunction) PyIscsiNode_setAuth,
METH_VARARGS|METH_KEYWORDS,
"Set authentication information"},
{"login", (PyCFunction) PyIscsiNode_login, METH_NOARGS,
"Log in to the node"},
{"logout", (PyCFunction) PyIscsiNode_logout, METH_NOARGS,
"Log out of the node"},
{"setParameter", (PyCFunction) PyIscsiNode_setParameter,
METH_VARARGS|METH_KEYWORDS,
"Set an iscsi node parameter"},
{"getParameter", (PyCFunction) PyIscsiNode_getParameter,
METH_VARARGS|METH_KEYWORDS,
"Get an iscsi node parameter"},
{NULL}
};
PyTypeObject PyIscsiNode_Type = {
PyObject_HEAD_INIT(NULL)
.tp_name = "libiscsi.node",
.tp_basicsize = sizeof (PyIscsiNode),
.tp_getset = PyIscsiNode_getseters,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE,
.tp_methods = PyIscsiNode_methods,
.tp_compare = (cmpfunc)PyIscsiNode_compare,
.tp_init = PyIscsiNode_init,
.tp_str = PyIscsiNode_str,
.tp_new = PyType_GenericNew,
.tp_doc = "The iscsi node contains iscsi node information.",
};
/***************************************************************************/
static PyObject *pylibiscsi_discover_sendtargets(PyObject *self,
PyObject *args, PyObject *kwds)
{
char *kwlist[] = {"address", "port", "authinfo", NULL};
const char *address = NULL;
int i, err, nr_found, port = 3260;
PyIscsiChapAuthInfo *pyauthinfo = NULL;
const struct libiscsi_auth_info *authinfo = NULL;
struct libiscsi_node *found_nodes;
PyObject* found_node_list;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|iO!",
kwlist, &address, &port,
&PyIscsiChapAuthInfo_Type,
&pyauthinfo))
return NULL;
if (pyauthinfo)
authinfo = &pyauthinfo->info;
err = libiscsi_discover_sendtargets(context, address, port, authinfo,
&nr_found, &found_nodes);
if (err) {
PyErr_SetString(PyExc_IOError,
libiscsi_get_error_string(context));
return NULL;
}
if (nr_found == 0)
Py_RETURN_NONE;
found_node_list = PyList_New(nr_found);
if (!found_node_list)
return NULL;
for(i = 0; i < nr_found; i++) {
PyIscsiNode *pynode;
pynode = PyObject_New(PyIscsiNode, &PyIscsiNode_Type);
if (!pynode) {
/* This will deref already added nodes for us */
Py_DECREF(found_node_list);
return NULL;
}
pynode->node = found_nodes[i];
PyList_SET_ITEM(found_node_list, i, (PyObject *)pynode);
}
return found_node_list;
}
static PyMethodDef pylibiscsi_functions[] = {
{ "discover_sendtargets",
(PyCFunction)pylibiscsi_discover_sendtargets,
METH_VARARGS|METH_KEYWORDS,
"Do sendtargets discovery and return a list of found nodes)"},
{NULL, NULL}
};
PyMODINIT_FUNC initlibiscsi(void)
{
PyObject *m;
context = libiscsi_init();
if (!context)
return;
if (PyType_Ready(&PyIscsiChapAuthInfo_Type) < 0)
return;
if (PyType_Ready(&PyIscsiNode_Type) < 0)
return;
m = Py_InitModule("libiscsi", pylibiscsi_functions);
Py_INCREF(&PyIscsiChapAuthInfo_Type);
PyModule_AddObject(m, "chapAuthInfo", (PyObject *) &PyIscsiChapAuthInfo_Type);
Py_INCREF(&PyIscsiNode_Type);
PyModule_AddObject(m, "node", (PyObject *) &PyIscsiNode_Type);
}
_______________________________________________
Anaconda-devel-list mailing list
Anaconda-devel-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/anaconda-devel-list