Youcef Laribi ec5da3cc2b Implements an LBaaS driver for NetScaler devices
This driver for the Neutron LBaaS plugin allows for
using the Citrix NetScaler loadbalancing devices
to provide Neutron LBaaS functionality in OpenStack.

Change-Id: Ibfeb54c4402943fb3696a1c599fa373e42e520d4
Implements: blueprint netscaler-lbaas-driver
2014-02-20 12:14:06 -08:00

183 lines
7.7 KiB
Python

# Copyright 2014 Citrix Systems
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import requests
from neutron.common import exceptions as n_exc
from neutron.openstack.common import jsonutils
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
CONTENT_TYPE_HEADER = 'Content-type'
ACCEPT_HEADER = 'Accept'
AUTH_HEADER = 'Authorization'
DRIVER_HEADER = 'X-OpenStack-LBaaS'
TENANT_HEADER = 'X-Tenant-ID'
JSON_CONTENT_TYPE = 'application/json'
DRIVER_HEADER_VALUE = 'netscaler-openstack-lbaas'
class NCCException(n_exc.NeutronException):
"""Represents exceptions thrown by NSClient."""
CONNECTION_ERROR = 1
REQUEST_ERROR = 2
RESPONSE_ERROR = 3
UNKNOWN_ERROR = 4
def __init__(self, error):
self.message = _("NCC Error %d") % error
super(NCCException, self).__init__()
self.error = error
class NSClient(object):
"""Client to operate on REST resources of NetScaler Control Center."""
def __init__(self, service_uri, username, password):
if not service_uri:
msg = _("No NetScaler Control Center URI specified. "
"Cannot connect.")
LOG.exception(msg)
raise NCCException(NCCException.CONNECTION_ERROR)
self.service_uri = service_uri.strip('/')
self.auth = None
if username and password:
base64string = base64.encodestring("%s:%s" % (username, password))
base64string = base64string[:-1]
self.auth = 'Basic %s' % base64string
def create_resource(self, tenant_id, resource_path, object_name,
object_data):
"""Create a resource of NetScaler Control Center."""
return self._resource_operation('POST', tenant_id,
resource_path,
object_name=object_name,
object_data=object_data)
def retrieve_resource(self, tenant_id, resource_path, parse_response=True):
"""Retrieve a resource of NetScaler Control Center."""
return self._resource_operation('GET', tenant_id, resource_path)
def update_resource(self, tenant_id, resource_path, object_name,
object_data):
"""Update a resource of the NetScaler Control Center."""
return self._resource_operation('PUT', tenant_id,
resource_path,
object_name=object_name,
object_data=object_data)
def remove_resource(self, tenant_id, resource_path, parse_response=True):
"""Remove a resource of NetScaler Control Center."""
return self._resource_operation('DELETE', tenant_id, resource_path)
def _resource_operation(self, method, tenant_id, resource_path,
object_name=None, object_data=None):
resource_uri = "%s/%s" % (self.service_uri, resource_path)
headers = self._setup_req_headers(tenant_id)
request_body = None
if object_data:
if isinstance(object_data, str):
request_body = object_data
else:
obj_dict = {object_name: object_data}
request_body = jsonutils.dumps(obj_dict)
response_status, resp_dict = self._execute_request(method,
resource_uri,
headers,
body=request_body)
return response_status, resp_dict
def _is_valid_response(self, response_status):
# when status is less than 400, the response is fine
return response_status < requests.codes.bad_request
def _setup_req_headers(self, tenant_id):
headers = {ACCEPT_HEADER: JSON_CONTENT_TYPE,
CONTENT_TYPE_HEADER: JSON_CONTENT_TYPE,
DRIVER_HEADER: DRIVER_HEADER_VALUE,
TENANT_HEADER: tenant_id,
AUTH_HEADER: self.auth}
return headers
def _get_response_dict(self, response):
response_dict = {'status': response.status_code,
'body': response.text,
'headers': response.headers}
if self._is_valid_response(response.status_code):
if response.text:
response_dict['dict'] = response.json()
return response_dict
def _execute_request(self, method, resource_uri, headers, body=None):
try:
response = requests.request(method, url=resource_uri,
headers=headers, data=body)
except requests.exceptions.ConnectionError:
msg = (_("Connection error occurred while connecting to %s") %
self.service_uri)
LOG.exception(msg)
raise NCCException(NCCException.CONNECTION_ERROR)
except requests.exceptions.SSLError:
msg = (_("SSL error occurred while connecting to %s") %
self.service_uri)
LOG.exception(msg)
raise NCCException(NCCException.CONNECTION_ERROR)
except requests.exceptions.Timeout:
msg = _("Request to %s timed out") % self.service_uri
LOG.exception(msg)
raise NCCException(NCCException.CONNECTION_ERROR)
except (requests.exceptions.URLRequired,
requests.exceptions.InvalidURL,
requests.exceptions.MissingSchema,
requests.exceptions.InvalidSchema):
msg = _("Request did not specify a valid URL")
LOG.exception(msg)
raise NCCException(NCCException.REQUEST_ERROR)
except requests.exceptions.TooManyRedirects:
msg = _("Too many redirects occurred for request to %s")
LOG.exception(msg)
raise NCCException(NCCException.REQUEST_ERROR)
except requests.exceptions.RequestException:
msg = (_("A request error while connecting to %s") %
self.service_uri)
LOG.exception(msg)
raise NCCException(NCCException.REQUEST_ERROR)
except Exception:
msg = (_("A unknown error occurred during request to %s") %
self.service_uri)
LOG.exception(msg)
raise NCCException(NCCException.UNKNOWN_ERROR)
resp_dict = self._get_response_dict(response)
LOG.debug(_("Response: %s"), resp_dict['body'])
response_status = resp_dict['status']
if response_status == requests.codes.unauthorized:
LOG.exception(_("Unable to login. Invalid credentials passed."
"for: %s"), self.service_uri)
raise NCCException(NCCException.RESPONSE_ERROR)
if not self._is_valid_response(response_status):
msg = (_("Failed %(method)s operation on %(url)s "
"status code: %(response_status)s") %
{"method": method,
"url": resource_uri,
"response_status": response_status})
LOG.exception(msg)
raise NCCException(NCCException.RESPONSE_ERROR)
return response_status, resp_dict