eBayClassifiedsGroup / nsnitro

Citrix Netscaler 9.2+ Python Library (NITRO API)
47 stars 50 forks source link

10.1 includes basic Python support #75

Open favoretti opened 10 years ago

favoretti commented 10 years ago

Hey,

We've just upgraded our netscalers to 10.1 and I found Python samples (using requests lib) inside netscaler itself.

It's located at /var/netscaler/nitro/ns-10.1-123.11-nitro-python-samples.tgz (YMMV depending on the build you're using).

Looking at it.. It looks somewhat raw.. Do we want to implement that into nsnitro as an alternative session constructor?

My other thought was to redo the nsnitro object in such a way that one can specify version to use and go from there. Whereas we could use the Nitro.py that Netscaler supplies.

Here's the implementation they made:

vlazarenko@xxx:~/lib$ cat Nitro.py
import requests
import json
import sys
import urllib.request
import urllib.parse

# Define a new Exception Class for Nitro Specific Errors
class NitroException(Exception):
    def __init__(self, severity, errorcode, message):
        self.severity = severity
        self.message = message
        self.errorcode = errorcode

# Login into Netscaler box and get sessionid
def _login( ip, user, passwd ):
    try:
        data = {}
        data['username']=user
        data['password']=passwd
        login_data={}
        login_data['login']=data
        data_json =json.dumps(login_data)
        headers = {'Content-type': 'application/vnd.com.citrix.netscaler.login+json','Connection': 'keep-alive','Accept':'application/vnd.com.citrix.netscaler.login+json'}
        url = "http://"+ip+"/nitro/v1/config/login"
        response = requests.post(url, data=data_json, headers=headers, timeout=15)
        if not response.ok:
            response = json.loads(response.text)
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            nitro_session = {}
            nitro_session['sessionid']=urllib.request.unquote(response.cookies['NITRO_AUTH_TOKEN'])
            nitro_session['errorcode'] = "0"
            nitro_session['message'] = "Done"
            nitro_session['severity']= "NONE"
            nitro_session['nsip']=ip;
            nitro_session['username']=user;
            nitro_session['password']=passwd;
            return nitro_session
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception:
        raise e

# Logout of Netscaler
def _logout( nitro_session ):
    try:
        payload={}
        payload['logout']={}
        if nitro_session is not None:
            headers = {'Content-type': 'application/vnd.com.citrix.netscaler.logout+json','Connection': 'keep-alive', 'Set-Cookie':'NITRO_AUTH_TOKEN='+nitro_session['sessionid']}
        else:
            raise Exception("Not logged in")
        data_json = json.dumps(payload)

        url = "http://"+nitro_session['nsip']+"/nitro/v1/config/logout"
        response = requests.post(url, data=data_json, headers=headers, timeout=15)
        if not response.ok:
            response = json.loads(response.text)
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            nitro_session = {}
            nitro_session['errorcode'] = "0"
            nitro_session['message'] = "Done"
            nitro_session['severity']= "NONE"
            return nitro_session
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# HTTP GET method : used for show commands
# Arguments : nitro_session, object-type, Object name, parameters
def _get( nitro_session, object_type, name="", params=""):
    try:
        payload={}
        if nitro_session is not None:
            headers = {'Content-type': 'application/vnd.com.citrix.netscaler.logout+json','Connection': 'keep-alive', 'Set-Cookie':'NITRO_AUTH_TOKEN='+nitro_session['sessionid']}
        else:
            raise Exception("Not logged in")
        if (name):
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type+"/"+urllib.parse.quote_plus(urllib.parse.quote_plus(name))
            if params is not None:
                url = url+"?"+params
        elif params is not None:
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type+"?"+params
        else:
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type
        response = requests.get(url, headers=headers, timeout=15)
        response = json.loads(response.text)
        if response['severity'] == "ERROR":
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            return response
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# HTTP POST method : used for add, rm, import, export, unset and other commands
# Arguments : nitro_session, operation, object-type, parameters
def _post( nitro_session, operation, object_type, _object, params=""):
    try:
        payload={}
        if nitro_session is not None:
            headers = {'Content-type': 'application/vnd.com.citrix.netscaler.'+ object_type +'+json','Connection': 'keep-alive', 'Set-Cookie':'NITRO_AUTH_TOKEN='+nitro_session['sessionid']}
        else:
            raise Exception("Not logged in")
        if operation != 'add':
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type+"?"+"action="+operation
            if (params):
                url = url + "&" + params
        else:
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type
            if (params):
                url = url + "?" + params
        payload[object_type]=_object
        payload_json=json.dumps(payload)

        response = requests.post(url, data=payload_json, headers=headers, timeout=15)
        if not response.ok:
            response = json.loads(response.text)
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            nitro_session = {}
            nitro_session['errorcode'] = "0"
            nitro_session['message'] = "Done"
            return nitro_session
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# HTTP DELETE method : used for rm and unbind commands
# Arguments : nitro_session, object-type, object name, parameters
def _delete( nitro_session, object_type, name, params=""):
    try:
        payload={}
        if nitro_session is not None:
            headers = {'Content-type': 'application/vnd.com.citrix.netscaler.'+ object_type +'+json','Connection': 'keep-alive', 'Set-Cookie':'NITRO_AUTH_TOKEN='+nitro_session['sessionid']}
        else:
            raise Exception("Not logged in")
        if (name) :
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type+"/"+urllib.parse.quote_plus(urllib.parse.quote_plus(name))
            if params is not None:
                url = url+"?"+params
        elif object_type == "application":
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type
            if params is not None:
                url = url+"?"+params
        else:
            raise Exception("Object name_value not specified")
        response = requests.delete(url, headers=headers, timeout=15)
        response = json.loads(response.text)
        if response['severity'] == "ERROR":
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            return response
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# HTTP PUT method : used for set, bind and other commands
# Arguments : nitro_session, operation, object-type, name argument id,parameters
def _put( nitro_session, operation, object_type, _object, name, params=""):
    try:
        payload={}
        if nitro_session is not None:
            headers = {'Content-type': 'application/vnd.com.citrix.netscaler.'+ object_type +'+json','Connection': 'keep-alive', 'Set-Cookie':'NITRO_AUTH_TOKEN='+nitro_session['sessionid']}
        else:
            raise Exception("Not logged in")
        if operation != 'set':
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type+"/"+urllib.parse.quote_plus(urllib.parse.quote_plus(name))+"?"+"action="+operation
            if (params):
                url = url + "&" + params
        else:
            url = "http://"+nitro_session['nsip']+"/nitro/v1/config/"+object_type+"/"+urllib.parse.quote_plus(urllib.parse.quote_plus(name))
            if (params):
                url = url + "?" + params

        payload[object_type]=_object
        payload_json=json.dumps(payload)

        response = requests.put(url, data=payload_json, headers=headers, timeout=15)
        if not response.ok:
            response = json.loads(response.text)
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            nitro_session = {}
            nitro_session['errorcode'] = "0"
            nitro_session['message'] = "Done"
            return nitro_session
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# HTTP GET method : used for stat commands
# Arguments : nitro_session, object-type, Object name, parameters
def _stat( nitro_session, object_type, name="", params=""):
    try:
        payload={}
        if nitro_session is not None:
            headers = {'Content-type': 'application/vnd.com.citrix.netscaler.logout+json','Connection': 'keep-alive', 'Set-Cookie':'NITRO_AUTH_TOKEN='+nitro_session['sessionid']}
        else:
            raise Exception("Not logged in")
        if (name):
            url = "http://"+nitro_session['nsip']+"/nitro/v1/stat/"+object_type+"/"+urllib.parse.quote_plus(urllib.parse.quote_plus(name))
            if params is not None:
                url = url+"?"+params
        elif params is not None:
            url = "http://"+nitro_session['nsip']+"/nitro/v1/stat/"+object_type+"?"+params
        else:
            url = "http://"+nitro_session['nsip']+"/nitro/v1/stat/"+object_type
        response = requests.get(url, headers=headers, timeout=15)
        response = json.loads(response.text)
        if response['severity'] == "ERROR":
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            return response
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# BULK GET method : uses GET method internally for bulk show (byname) commands
# Arguments : nitro_session, object-type, Object name, parameters
def _bulkget( nitro_session, object_type, name="", params=""):
    try:
        payload={}
        if nitro_session is None:
            raise Exception("Not logged in")
        list = []
        if (name):
            for key in name:
                response = _get(nitro_session, object_type, key, params)
                list.append(response)
        else:
            raise Exception("Object name_value not specified")
        if response['severity'] == "ERROR":
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            return list
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# BULK DELETE method : uses DELETE method internally for bulk rm and unbind commands
# Arguments : nitro_session, object-type, object name, parameters
def _bulkdelete( nitro_session, object_type, name="", params=""):
    try:
        payload={}
        if nitro_session is None:
            raise Exception("Not logged in")
        list = []
        if (name):
            for key in name:
                response = _delete(nitro_session, object_type, key, params)
                list.append(response)
        else:
            raise Exception("Object name_value not specified")
        if response['severity'] == "ERROR":
            raise NitroException((str(response['severity'])), str(response['errorcode']), str(response['message']))
        else:
            return list
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

# BULK POST method : uses POST method internally for bulk add, rm and other commands
# Arguments : nitro_session, operation, object-type, object name,parameters
def _bulkpost( nitro_session, operation, object_type, _object, params=""):
    try:
        payload={}
        if nitro_session is None:
            raise Exception("Not logged in")
        list = []
        if (_object):
            for key in _object:
                response = _post(nitro_session, operation, object_type, key, params)
                list.append(response)
        else:
            raise Exception("Object name_value not specified")
        return list
    except NitroException as e:
        raise e
    except requests.exceptions.ConnectionError:
        raise Exception("Connection Error")
    except requests.exceptions.Timeout:
        raise Exception("Request Timed Out")
    except Exception as e:
        raise e

Any opinions?

robison commented 10 years ago

Honestly, I'd really like to see this used as a session constructor (and requests while we're at it!), but either path would be great. What do you think the relative complexity would be, between the two options?

Also, I've heard from some sources that a full Python API is being planned for 10.5 or sooner... not sure what the actual timeframe is on that, however.

favoretti commented 10 years ago

What I don't like about this implementation is how it's supposed to be used later on...

                clearconfig_obj = {}
                clearconfig_obj['force'] = "1"
                clearconfig_obj['level'] = "full"
                response = post(session, "clear", "nsconfig", clearconfig_obj)

Basically it looks more like combining json objects by hand, rather than an "object-like" syntax.. Dunno, maybe it's just me.

favoretti commented 10 years ago

Got access to 10.5 beta from Citrix. As soon as I get hands on official Python API, I'll report back. My greatest fear is is that it's all dict() based, which would mean we, for instance, at eCG, will need to rewrite everything we've done so far using nsnitro.. and we have a lot.

Good thing is that in the meantime I could just steal the base object from them, integrate here and support this one until official Python SDK will be adopted by everyone.

Anyway, I'll use this issue to share some insights on how official Python SDK looks like.

robison commented 10 years ago

I've got a copy of the Python SDK. The base login object (base_resource.py) uses requests, json, abc, urllib2, and inspect. Also, looks like it's a near-direct port of the Java SDK, complete with the class naming scheme, i.e.:

from src.com.citrix.netscaler.nitro.service.options import options from src.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception from src.com.citrix.netscaler.nitro.resource.base.Json import Json

Also: bulk operations look to be fully supported! Digging into it more this morning.

favoretti commented 10 years ago

Is it dict-based or getter/setter based?

favoretti commented 10 years ago

Also, whyyyy are they using both requests and urllib2?