VMWare NSXv: Common components

For Kilo, it was decided to split vendor code into stackforge repos, while
services (*aas) code is moved to neutron-*aas repos.
Some components are common to VMWare NSXv neutron plugin and services
plugins.
As we do not want to add dependencies between the neutron-lbaas, neutron-fwaas
repos and the stackforge/vmware-nsx repo, (nor duplicate the code), we move
these common components to oslo.vmware under oslo.vmware.network

Change-Id: If3c98bc9431253768914316db6b48d1777518353
Partially-Implements: blueprint vmware-nsx-v
This commit is contained in:
Kobi Samoray 2015-01-13 09:01:03 +02:00
parent c50d927c07
commit 3356e2e147
19 changed files with 2707 additions and 0 deletions

View File

View File

View File

View File

@ -0,0 +1,598 @@
# Copyright 2015 VMware, Inc
#
# 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 logging
import time
import xml.etree.ElementTree as et
from oslo_serialization import jsonutils
from oslo_vmware._i18n import _LI
from oslo_vmware.network.nsx.nsxv.api import api_helper
from oslo_vmware.network.nsx.nsxv.common import exceptions
LOG = logging.getLogger(__name__)
HTTP_GET = "GET"
HTTP_POST = "POST"
HTTP_DELETE = "DELETE"
HTTP_PUT = "PUT"
URI_PREFIX = "/api/4.0/edges"
# FwaaS constants
FIREWALL_SERVICE = "firewall/config"
FIREWALL_RULE_RESOURCE = "rules"
# NSXv Constants
FIREWALL_PREFIX = '/api/4.0/firewall/globalroot-0/config'
SECURITYGROUP_PREFIX = '/api/2.0/services/securitygroup'
VDN_PREFIX = '/api/2.0/vdn'
SERVICES_PREFIX = '/api/2.0/services'
# LbaaS Constants
LOADBALANCER_SERVICE = "loadbalancer/config"
VIP_RESOURCE = "virtualservers"
POOL_RESOURCE = "pools"
MONITOR_RESOURCE = "monitors"
APP_PROFILE_RESOURCE = "applicationprofiles"
APP_RULE_RESOURCE = "applicationrules"
# IPsec VPNaaS Constants
IPSEC_VPN_SERVICE = 'ipsec/config'
# Dhcp constants
DHCP_SERVICE = "dhcp/config"
DHCP_BINDING_RESOURCE = "bindings"
class NsxvApi(object):
def __init__(self, address, user, password, retries=2):
self.address = address
self.user = user
self.password = password
self.retries = retries
self.jsonapi_client = api_helper.NsxvApiHelper(address, user,
password, 'json')
self.xmlapi_client = api_helper.NsxvApiHelper(address, user,
password, 'xml')
def _client_request(self, client, method, uri, params, headers,
encode_params):
retries = max(self.retries, 1)
delay = 0.5
for attempt in range(1, retries + 1):
if attempt != 1:
time.sleep(delay)
delay = min(2 * delay, 60)
try:
return client(method, uri, params, headers, encode_params)
except exceptions.ServiceConflict as e:
if attempt == retries:
raise e
LOG.info(_LI('NSXv: conflict on request. Trying again.'))
def do_request(self, method, uri, params=None, format='json', **kwargs):
LOG.debug("NsxvApi('%(method)s', '%(uri)s', '%(body)s')", {
'method': method,
'uri': uri,
'body': params})
headers = kwargs.get('headers')
encode_params = kwargs.get('encode', True)
if format == 'json':
_client = self.jsonapi_client.request
else:
_client = self.xmlapi_client.request
header, content = self._client_request(_client, method, uri, params,
headers, encode_params)
if content == '':
return header, {}
if kwargs.get('decode', True):
content = jsonutils.loads(content)
return header, content
def deploy_edge(self, request):
uri = URI_PREFIX + "?async=true"
return self.do_request(HTTP_POST, uri, request, decode=False)
def update_edge(self, edge_id, request):
uri = "%s/%s?async=true" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_PUT, uri, request, decode=False)
def get_edge_id(self, job_id):
uri = URI_PREFIX + "/jobs/%s" % job_id
return self.do_request(HTTP_GET, uri, decode=True)
def get_edge_jobs(self, edge_id):
uri = URI_PREFIX + "/%s/jobs" % edge_id
return self.do_request(HTTP_GET, uri, decode=True)
def get_edge_deploy_status(self, edge_id):
uri = URI_PREFIX + "/%s/status?getlatest=false" % edge_id
return self.do_request(HTTP_GET, uri, decode="True")
def delete_edge(self, edge_id):
uri = "%s/%s" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_DELETE, uri)
def add_vdr_internal_interface(self, edge_id, interface):
uri = "%s/%s/interfaces?action=patch&async=true" % (URI_PREFIX,
edge_id)
return self.do_request(HTTP_POST, uri, interface, decode=True)
def update_vdr_internal_interface(
self, edge_id, interface_index, interface):
uri = "%s/%s/interfaces/%s?async=true" % (URI_PREFIX, edge_id,
interface_index)
return self.do_request(HTTP_PUT, uri, interface, decode=True)
def delete_vdr_internal_interface(self, edge_id, interface_index):
uri = "%s/%s/interfaces/%d?async=true" % (URI_PREFIX, edge_id,
interface_index)
return self.do_request(HTTP_DELETE, uri, decode=True)
def get_interfaces(self, edge_id):
uri = "%s/%s/vnics" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_GET, uri, decode=True)
def update_interface(self, edge_id, vnic):
uri = "%s/%s/vnics/%d?async=true" % (URI_PREFIX, edge_id,
vnic['index'])
return self.do_request(HTTP_PUT, uri, vnic, decode=True)
def delete_interface(self, edge_id, vnic_index):
uri = "%s/%s/vnics/%d?async=true" % (URI_PREFIX, edge_id, vnic_index)
return self.do_request(HTTP_DELETE, uri, decode=True)
def get_nat_config(self, edge_id):
uri = "%s/%s/nat/config" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_GET, uri, decode=True)
def update_nat_config(self, edge_id, nat):
uri = "%s/%s/nat/config?async=true" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_PUT, uri, nat, decode=True)
def delete_nat_rule(self, edge_id, rule_id):
uri = "%s/%s/nat/config/rules/%s" % (URI_PREFIX, edge_id, rule_id)
return self.do_request(HTTP_DELETE, uri, decode=True)
def get_edge_status(self, edge_id):
uri = "%s/%s/status?getlatest=false" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_GET, uri, decode=True)
def get_edges(self):
uri = URI_PREFIX
return self.do_request(HTTP_GET, uri, decode=True)
def get_edge_interfaces(self, edge_id):
uri = "%s/%s/interfaces" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_GET, uri, decode=True)
def update_routes(self, edge_id, routes):
uri = "%s/%s/routing/config/static?async=true" % (URI_PREFIX, edge_id)
return self.do_request(HTTP_PUT, uri, routes)
def create_lswitch(self, lsconfig):
uri = "/api/ws.v1/lswitch"
return self.do_request(HTTP_POST, uri, lsconfig, decode=True)
def delete_lswitch(self, lswitch_id):
uri = "/api/ws.v1/lswitch/%s" % lswitch_id
return self.do_request(HTTP_DELETE, uri)
def get_loadbalancer_config(self, edge_id):
uri = self._build_uri_path(edge_id, LOADBALANCER_SERVICE)
return self.do_request(HTTP_GET, uri, decode=True)
def enable_service_loadbalancer(self, edge_id, config):
uri = self._build_uri_path(edge_id, LOADBALANCER_SERVICE)
return self.do_request(HTTP_PUT, uri, config)
def update_firewall(self, edge_id, fw_req):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE)
uri += '?async=true'
return self.do_request(HTTP_PUT, uri, fw_req)
def delete_firewall(self, edge_id):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE, None)
uri += '?async=true'
return self.do_request(HTTP_DELETE, uri)
def update_firewall_rule(self, edge_id, vcns_rule_id, fwr_req):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE,
FIREWALL_RULE_RESOURCE,
vcns_rule_id)
return self.do_request(HTTP_PUT, uri, fwr_req)
def delete_firewall_rule(self, edge_id, vcns_rule_id):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE,
FIREWALL_RULE_RESOURCE,
vcns_rule_id)
return self.do_request(HTTP_DELETE, uri)
def add_firewall_rule_above(self, edge_id, ref_vcns_rule_id, fwr_req):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE,
FIREWALL_RULE_RESOURCE)
uri += "?aboveRuleId=" + ref_vcns_rule_id
return self.do_request(HTTP_POST, uri, fwr_req)
def add_firewall_rule(self, edge_id, fwr_req):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE,
FIREWALL_RULE_RESOURCE)
return self.do_request(HTTP_POST, uri, fwr_req)
def get_firewall(self, edge_id):
uri = self._build_uri_path(edge_id, FIREWALL_SERVICE)
return self.do_request(HTTP_GET, uri, decode=True)
def get_firewall_rule(self, edge_id, vcns_rule_id):
uri = self._build_uri_path(
edge_id, FIREWALL_SERVICE,
FIREWALL_RULE_RESOURCE,
vcns_rule_id)
return self.do_request(HTTP_GET, uri, decode=True)
#
# Edge LBAAS call helper
#
def create_vip(self, edge_id, vip_new):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
VIP_RESOURCE)
return self.do_request(HTTP_POST, uri, vip_new)
def get_vip(self, edge_id, vip_vseid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
VIP_RESOURCE, vip_vseid)
return self.do_request(HTTP_GET, uri, decode=True)
def update_vip(self, edge_id, vip_vseid, vip_new):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
VIP_RESOURCE, vip_vseid)
return self.do_request(HTTP_PUT, uri, vip_new)
def delete_vip(self, edge_id, vip_vseid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
VIP_RESOURCE, vip_vseid)
return self.do_request(HTTP_DELETE, uri)
def create_pool(self, edge_id, pool_new):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
POOL_RESOURCE)
return self.do_request(HTTP_POST, uri, pool_new)
def get_pool(self, edge_id, pool_vseid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
POOL_RESOURCE, pool_vseid)
return self.do_request(HTTP_GET, uri, decode=True)
def update_pool(self, edge_id, pool_vseid, pool_new):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
POOL_RESOURCE, pool_vseid)
return self.do_request(HTTP_PUT, uri, pool_new)
def delete_pool(self, edge_id, pool_vseid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
POOL_RESOURCE, pool_vseid)
return self.do_request(HTTP_DELETE, uri)
def create_health_monitor(self, edge_id, monitor_new):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
MONITOR_RESOURCE)
return self.do_request(HTTP_POST, uri, monitor_new)
def get_health_monitor(self, edge_id, monitor_vseid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
MONITOR_RESOURCE, monitor_vseid)
return self.do_request(HTTP_GET, uri, decode=True)
def update_health_monitor(self, edge_id, monitor_vseid, monitor_new):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
MONITOR_RESOURCE,
monitor_vseid)
return self.do_request(HTTP_PUT, uri, monitor_new)
def delete_health_monitor(self, edge_id, monitor_vseid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
MONITOR_RESOURCE,
monitor_vseid)
return self.do_request(HTTP_DELETE, uri)
def create_app_profile(self, edge_id, app_profile):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
APP_PROFILE_RESOURCE)
return self.do_request(HTTP_POST, uri, app_profile)
def update_app_profile(self, edge_id, app_profileid, app_profile):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
APP_PROFILE_RESOURCE, app_profileid)
return self.do_request(HTTP_PUT, uri, app_profile)
def delete_app_profile(self, edge_id, app_profileid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
APP_PROFILE_RESOURCE,
app_profileid)
return self.do_request(HTTP_DELETE, uri)
def create_app_rule(self, edge_id, app_rule):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
APP_RULE_RESOURCE)
return self.do_request(HTTP_POST, uri, app_rule)
def update_app_rule(self, edge_id, app_ruleid, app_rule):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
APP_RULE_RESOURCE, app_ruleid)
return self.do_request(HTTP_PUT, uri, app_rule)
def delete_app_rule(self, edge_id, app_ruleid):
uri = self._build_uri_path(
edge_id, LOADBALANCER_SERVICE,
APP_RULE_RESOURCE,
app_ruleid)
return self.do_request(HTTP_DELETE, uri)
def update_ipsec_config(self, edge_id, ipsec_config):
uri = self._build_uri_path(edge_id, IPSEC_VPN_SERVICE)
return self.do_request(HTTP_PUT, uri, ipsec_config)
def delete_ipsec_config(self, edge_id):
uri = self._build_uri_path(edge_id, IPSEC_VPN_SERVICE)
return self.do_request(HTTP_DELETE, uri)
def get_ipsec_config(self, edge_id):
uri = self._build_uri_path(edge_id, IPSEC_VPN_SERVICE)
return self.do_request(HTTP_GET, uri)
def create_virtual_wire(self, vdn_scope_id, request):
"""Creates a VXLAN virtual wire
The method will return the virtual wire ID.
"""
uri = '/api/2.0/vdn/scopes/%s/virtualwires' % vdn_scope_id
return self.do_request(HTTP_POST, uri, request, format='xml',
decode=False)
def delete_virtual_wire(self, virtualwire_id):
"""Deletes a virtual wire."""
uri = '/api/2.0/vdn/virtualwires/%s' % virtualwire_id
return self.do_request(HTTP_DELETE, uri, format='xml')
def create_port_group(self, dvs_id, request):
"""Creates a port group on a DVS
The method will return the port group ID.
"""
uri = '/api/2.0/xvs/switches/%s/networks' % dvs_id
return self.do_request(HTTP_POST, uri, request, format='xml',
decode=False)
def delete_port_group(self, dvs_id, portgroup_id):
"""Deletes a portgroup."""
uri = '/api/2.0/xvs/switches/%s/networks/%s' % (dvs_id,
portgroup_id)
return self.do_request(HTTP_DELETE, uri, format='xml', decode=False)
def query_interface(self, edge_id, vnic_index):
uri = "%s/%s/vnics/%d" % (URI_PREFIX, edge_id, vnic_index)
return self.do_request(HTTP_GET, uri, decode=True)
def reconfigure_dhcp_service(self, edge_id, request_config):
"""Reconfigure dhcp static bindings in the created Edge."""
uri = "/api/4.0/edges/%s/dhcp/config?async=true" % edge_id
return self.do_request(HTTP_PUT, uri, request_config)
def query_dhcp_configuration(self, edge_id):
"""Query DHCP configuration from the specific edge."""
uri = "/api/4.0/edges/%s/dhcp/config" % edge_id
return self.do_request(HTTP_GET, uri)
def create_dhcp_binding(self, edge_id, request_config):
"""Append one dhcp static binding on the edge."""
uri = self._build_uri_path(edge_id,
DHCP_SERVICE, DHCP_BINDING_RESOURCE,
is_async=True)
return self.do_request(HTTP_POST, uri, request_config, decode=False)
def delete_dhcp_binding(self, edge_id, binding_id):
"""Delete one dhcp static binding on the edge."""
uri = self._build_uri_path(edge_id,
DHCP_SERVICE, DHCP_BINDING_RESOURCE,
binding_id, is_async=True)
return self.do_request(HTTP_DELETE, uri, decode=False)
def create_security_group(self, request):
"""Creates a security group container in nsx.
The method will return the security group ID.
"""
uri = '%s/globalroot-0' % SECURITYGROUP_PREFIX
return self.do_request(HTTP_POST, uri, request, format='xml',
decode=False)
def delete_security_group(self, securitygroup_id):
"""Deletes a security group container."""
uri = '%s/%s?force=true' % (SECURITYGROUP_PREFIX, securitygroup_id)
return self.do_request(HTTP_DELETE, uri, format='xml', decode=False)
def create_section(self, type, request):
"""Creates a layer 3 or layer 2 section in nsx rule table.
The method will return the uri to newly created section.
"""
if type == 'ip':
sec_type = 'layer3sections'
else:
sec_type = 'layer2sections'
uri = '%s/%s?autoSaveDraft=false' % (FIREWALL_PREFIX, sec_type)
return self.do_request(HTTP_POST, uri, request, format='xml',
decode=False, encode=False)
def update_section(self, section_uri, request, h):
"""Replaces a section in nsx rule table."""
uri = '%s?autoSaveDraft=false' % section_uri
headers = self._get_section_header(section_uri, h)
return self.do_request(HTTP_PUT, uri, request, format='xml',
decode=False, encode=False, headers=headers)
def delete_section(self, section_uri):
"""Deletes a section in nsx rule table."""
uri = '%s?autoSaveDraft=false' % section_uri
return self.do_request(HTTP_DELETE, uri, format='xml', decode=False)
def get_section(self, section_uri):
return self.do_request(HTTP_GET, section_uri, format='xml',
decode=False)
def get_section_id(self, section_name):
"""Retrieve the id of a section from nsx."""
uri = FIREWALL_PREFIX
h, section_list = self.do_request(HTTP_GET, uri, decode=False,
format='xml')
root = et.fromstring(section_list)
for elem in root.findall('.//*'):
if elem.tag == 'section' and elem.attrib['name'] == section_name:
return elem.attrib['id']
def update_section_by_id(self, id, type, request):
"""Update a section while building its uri from the id."""
if type == 'ip':
sec_type = 'layer3sections'
else:
sec_type = 'layer2sections'
section_uri = '%s/%s/%s' % (FIREWALL_PREFIX, sec_type, id)
self.update_section(section_uri, request, h=None)
def _get_section_header(self, section_uri, h=None):
if h is None:
h, c = self.do_request(HTTP_GET, section_uri, format='xml',
decode=False)
etag = h['etag']
headers = {'If-Match': etag}
return headers
def remove_rule_from_section(self, section_uri, rule_id):
"""Deletes a rule from nsx section table."""
uri = '%s/rules/%s?autoSaveDraft=false' % (section_uri, rule_id)
headers = self._get_section_header(section_uri)
return self.do_request(HTTP_DELETE, uri, format='xml',
headers=headers)
def add_member_to_security_group(self, security_group_id, member_id):
"""Adds a vnic member to nsx security group."""
uri = '%s/%s/members/%s' % (SECURITYGROUP_PREFIX,
security_group_id, member_id)
return self.do_request(HTTP_PUT, uri, format='xml', decode=False)
def remove_member_from_security_group(self, security_group_id,
member_id):
"""Removes a vnic member from nsx security group."""
uri = '%s/%s/members/%s' % (SECURITYGROUP_PREFIX,
security_group_id, member_id)
return self.do_request(HTTP_DELETE, uri, format='xml', decode=False)
def _build_uri_path(self, edge_id,
service,
resource=None,
resource_id=None,
parent_resource_id=None,
fields=None,
relations=None,
filters=None,
types=None,
is_attachment=False,
is_async=False):
uri_prefix = "%s/%s/%s" % (URI_PREFIX, edge_id, service)
if resource:
res_path = resource + (resource_id and "/%s" % resource_id or '')
uri_path = "%s/%s" % (uri_prefix, res_path)
else:
uri_path = uri_prefix
if is_async:
return uri_path + "?async=true"
else:
return uri_path
def _scopingobjects_lookup(self, type_name, object_id):
uri = '%s/usermgmt/scopingobjects' % SERVICES_PREFIX
h, so_list = self.do_request(HTTP_GET, uri, decode=False,
format='xml')
root = et.fromstring(so_list)
for elem in root.findall('.//*'):
if(elem.tag == 'object'
and elem.find('objectTypeName').text == type_name
and elem.find('objectId').text == object_id):
return True
return False
def validate_datacenter_moid(self, object_id):
return self._scopingobjects_lookup('Datacenter', object_id)
def validate_network(self, object_id):
return (self._scopingobjects_lookup('Network', object_id) or
self._scopingobjects_lookup('DistributedVirtualPortgroup',
object_id) or
self._scopingobjects_lookup('VirtualWire', object_id))
def validate_vdn_scope(self, object_id):
uri = '%s/scopes' % VDN_PREFIX
h, scope_list = self.do_request(HTTP_GET, uri, decode=False,
format='xml')
root = et.fromstring(scope_list)
for elem in root.findall('.//*'):
if elem.tag == 'objectId' and elem.text == object_id:
return True
return False
def validate_dvs(self, object_id):
uri = '%s/switches' % VDN_PREFIX
h, dvs_list = self.do_request(HTTP_GET, uri, decode=False,
format='xml')
root = et.fromstring(dvs_list)
for elem in root.findall('.//*'):
if elem.tag == 'objectId' and elem.text == object_id:
return True
return False

View File

@ -0,0 +1,117 @@
# Copyright 2015 VMware, Inc
#
# 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 eventlet
from oslo_serialization import jsonutils
import six
from oslo_vmware.network.nsx.nsxv.common import exceptions
httplib2 = eventlet.import_patched('httplib2')
def _xmldump(obj):
"""Sort of imporved xml creation method.
This converts the dict to xml with following assumptions:
keys starting with _(underscore) are to be used as attributes and not
elements keys starting with @ are to there so that dict can be made.
The keys are not part of any xml schema.
"""
config = ""
attr = ""
if isinstance(obj, dict):
for key, value in six.iteritems(obj):
if (key.startswith('_')):
attr += ' %s="%s"' % (key[1:], value)
else:
a, x = _xmldump(value)
if (key.startswith('@')):
cfg = "%s" % (x)
else:
cfg = "<%s%s>%s</%s>" % (key, a, x, key)
config += cfg
elif isinstance(obj, list):
for value in obj:
a, x = _xmldump(value)
attr += a
config += x
else:
config = obj
return attr, config
def xmldumps(obj):
attr, xml = _xmldump(obj)
return xml
class NsxvApiHelper(object):
errors = {
303: exceptions.ResourceRedirect,
400: exceptions.RequestBad,
403: exceptions.Forbidden,
404: exceptions.ResourceNotFound,
409: exceptions.ServiceConflict,
415: exceptions.MediaTypeUnsupport,
503: exceptions.ServiceUnavailable
}
def __init__(self, address, user, password, format='json'):
self.authToken = base64.b64encode(
str.encode("%s:%s" % (user, password))).decode('ascii')
self.user = user
self.passwd = password
self.address = address
self.format = format
if format == 'json':
self.encode = jsonutils.dumps
else:
self.encode = xmldumps
def _http_request(self, uri, method, body, headers):
http = httplib2.Http()
http.disable_ssl_certificate_validation = True
return http.request(uri, method, body=body, headers=headers)
def request(self, method, uri, params=None, headers=None,
encodeparams=True):
uri = self.address + uri
if headers is None:
headers = {}
headers['Content-Type'] = 'application/' + self.format
headers['Accept'] = 'application/' + self.format,
headers['Authorization'] = 'Basic ' + self.authToken
if encodeparams is True:
body = self.encode(params) if params else None
else:
body = params if params else None
header, response = self._http_request(uri, method,
body=body, headers=headers)
status = int(header['status'])
if 200 <= status < 300:
return header, response
if status in self.errors:
cls = self.errors[status]
else:
cls = exceptions.NsxvApiException
raise cls(uri=uri, status=status, header=header, response=response)

View File

@ -0,0 +1,97 @@
# Copyright 2015 VMware, Inc
#
# 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.
from oslo_utils import excutils
from oslo_vmware._i18n import _
class NsxvException(Exception):
"""Base Neutron Exception.
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
message = _("An unknown exception occurred.")
def __init__(self, **kwargs):
try:
super(NsxvException, self).__init__(self.message % kwargs)
self.msg = self.message % kwargs
except Exception:
with excutils.save_and_reraise_exception() as ctxt:
if not self.use_fatal_exceptions():
ctxt.reraise = False
# at least get the core message out if something happened
super(NsxvException, self).__init__(self.message)
def __unicode__(self):
return unicode(self.msg)
def use_fatal_exceptions(self):
return False
class NsxvGeneralException(NsxvException):
def __init__(self, message):
self.message = message
super(NsxvGeneralException, self).__init__()
class NsxvBadRequest(NsxvException):
message = _('Bad %(resource)s request: %(msg)s')
class NsxvNotFound(NsxvException):
message = _('%(resource)s not found: %(msg)s')
class NsxvApiException(NsxvException):
message = _("An unknown exception %(status)s occurred: %(response)s.")
def __init__(self, **kwargs):
super(NsxvApiException, self).__init__(**kwargs)
self.status = kwargs.get('status')
self.header = kwargs.get('header')
self.response = kwargs.get('response')
class ResourceRedirect(NsxvApiException):
message = _("Resource %(uri)s has been redirected")
class RequestBad(NsxvApiException):
message = _("Request %(uri)s is Bad, response %(response)s")
class Forbidden(NsxvApiException):
message = _("Forbidden: %(uri)s")
class ResourceNotFound(NsxvApiException):
message = _("Resource %(uri)s not found")
class MediaTypeUnsupport(NsxvApiException):
message = _("Media Type %(uri)s is not supported")
class ServiceUnavailable(NsxvApiException):
message = _("Service Unavailable: %(uri)s")
class ServiceConflict(NsxvApiException):
message = _("Concurrent object access error: %(uri)s")

View File

@ -0,0 +1,67 @@
# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
# 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 abc
from oslo_serialization import jsonutils
import six
from oslo_vmware.network.nsx.nsxv.api import api
@six.add_metaclass(abc.ABCMeta)
class NsxvEdgeCfgObj(object):
def __init__(self):
return
@abc.abstractmethod
def get_service_name(self):
return
@abc.abstractmethod
def serializable_payload(self):
return
@staticmethod
def get_object(nsxv_api, edge_id, service_name):
uri = "%s/%s/%s" % (api.URI_PREFIX,
edge_id,
service_name)
h, v = nsxv_api.do_request(
api.HTTP_GET,
uri,
decode=True)
return v
def submit_to_backend(self, nsxv_api, edge_id, async=True):
uri = "%s/%s/%s/config" % (api.URI_PREFIX,
edge_id,
self.get_service_name())
if async:
uri += '?async=true'
payload = jsonutils.dumps(self.serializable_payload(), sort_keys=True)
if payload:
return nsxv_api.do_request(
api.HTTP_PUT,
uri,
payload,
format='json',
encode=False)

View File

@ -0,0 +1,392 @@
# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
# 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 logging
from oslo_vmware.network.nsx.nsxv.objects import edge_cfg_obj
LOG = logging.getLogger(__name__)
class NsxvLoadbalancer(edge_cfg_obj.NsxvEdgeCfgObj):
SERVICE_NAME = 'loadbalancer'
def __init__(
self,
enabled=True,
enable_service_insertion=False,
acceleration_enabled=False):
super(NsxvLoadbalancer, self).__init__()
self.payload = {
'enabled': enabled,
'enableServiceInsertion': enable_service_insertion,
'accelerationEnabled': acceleration_enabled}
self.virtual_servers = {}
def get_service_name(self):
return self.SERVICE_NAME
def add_virtual_server(self, virtual_server):
self.virtual_servers[virtual_server.payload['name']] = virtual_server
def del_virtual_server(self, name):
self.virtual_servers.pop(name, None)
def serializable_payload(self):
virt_servers = []
app_profiles = []
app_rules = []
pools = []
monitors = []
virt_id = 1
app_prof_id = 1
app_rule_id = 1
pool_id = 1
monitor_id = 1
member_id = 1
for virtual_server in self.virtual_servers.values():
s_virt = virtual_server.payload.copy()
s_virt['virtualServerId'] = 'virtualServer-%d' % virt_id
virt_id += 1
# Setup app profile
s_app_prof = virtual_server.app_profile.payload.copy()
s_app_prof['applicationProfileId'] = ('applicationProfile-%d' %
app_prof_id)
app_profiles.append(s_app_prof)
app_prof_id += 1
# Bind virtual server to app profile
s_virt['applicationProfileId'] = s_app_prof['applicationProfileId']
# Setup app rules
if virtual_server.app_rules.values():
s_virt['applicationRuleId'] = []
for app_rule in virtual_server.app_rules.values():
s_app_rule = app_rule.payload.copy()
s_app_rule['applicationRuleId'] = ('applicationRule-%d' %
app_rule_id)
app_rule_id += 1
# Add to LB object, bind to virtual server
app_rules.append(s_app_rule)
s_virt['applicationRuleId'].append(
s_app_rule['applicationRuleId'])
# Setup pools
s_pool = virtual_server.default_pool.payload.copy()
s_pool['poolId'] = 'pool-%d' % pool_id
pool_id += 1
pools.append(s_pool)
# Add pool members
s_pool['member'] = []
for member in virtual_server.default_pool.members.values():
s_m = member.payload.copy()
s_m['memberId'] = 'member-%d' % member_id
member_id += 1
s_pool['member'].append(s_m)
# Bind pool to virtual server
s_virt['defaultPoolId'] = s_pool['poolId']
s_pool['monitorId'] = []
# Add monitors
for monitor in virtual_server.default_pool.monitors.values():
s_mon = monitor.payload.copy()
s_mon['monitorId'] = 'monitor-%d' % monitor_id
monitor_id += 1
s_pool['monitorId'].append(s_mon['monitorId'])
monitors.append(s_mon)
virt_servers.append(s_virt)
payload = self.payload.copy()
payload['applicationProfile'] = app_profiles
if app_rules:
payload['applicationRule'] = app_rules
payload['monitor'] = monitors
payload['pool'] = pools
payload['virtualServer'] = virt_servers
payload['featureType'] = 'loadbalancer_4.0'
return payload
@staticmethod
def get_loadbalancer(nsxv_api, edge_id):
edge_lb = edge_cfg_obj.NsxvEdgeCfgObj.get_object(
nsxv_api,
edge_id,
NsxvLoadbalancer.SERVICE_NAME)
lb_obj = NsxvLoadbalancer(
edge_lb['enabled'],
edge_lb['enableServiceInsertion'],
edge_lb['accelerationEnabled'])
# Construct loadbalancer objects
for virt_srvr in edge_lb['virtualServer']:
v_s = NsxvLBVirtualServer(
virt_srvr['name'],
virt_srvr['ipAddress'],
virt_srvr['port'],
virt_srvr['protocol'],
virt_srvr['enabled'],
virt_srvr['accelerationEnabled'],
virt_srvr['connectionLimit'])
# Find application profile objects, attach to virtual server
for app_prof in edge_lb['applicationProfile']:
if (virt_srvr['applicationProfileId']
== app_prof['applicationProfileId']):
a_p = NsxvLBAppProfile(
app_prof['name'],
app_prof['serverSslEnabled'],
app_prof['sslPassthrough'],
app_prof['template'],
app_prof['insertXForwardedFor'])
if app_prof['persistence']:
a_p.set_persistence(
True,
app_prof['persistence']['method'],
app_prof['persistence'].get('cookieName'),
app_prof['persistence'].get('cookieMode'),
app_prof['persistence'].get('expire'))
v_s.set_app_profile(a_p)
# Find default pool, attach to virtual server
for pool in edge_lb['pool']:
if virt_srvr['defaultPoolId'] == pool['poolId']:
p = NsxvLBPool(
pool['name'],
pool['algorithm'],
pool['transparent'])
# Add pool members to pool
for member in pool['member']:
m = NsxvLBPoolMember(
member['name'],
member['ipAddress'],
member['port'],
member['monitorPort'],
member['condition'],
member['weight'],
member['minConn'],
member['maxConn'])
p.add_member(m)
# Add monitors to pool
for mon in edge_lb['monitor']:
if mon['monitorId'] in pool['monitorId']:
m = NsxvLBMonitor(
mon['name'],
mon['interval'],
mon['maxRetries'],
mon['method'],
mon['timeout'],
mon['type'],
mon['url'])
p.add_monitor(m)
v_s.set_default_pool(p)
# Add application rules to virtual server
for rule in edge_lb['applicationRule']:
if rule['applicationRuleId'] in virt_srvr['applicationRuleId']:
r = NsxvLBAppRule(
rule['name'],
rule['script'])
v_s.add_app_rule(r)
lb_obj.add_virtual_server(v_s)
return lb_obj
class NsxvLBAppProfile():
def __init__(
self,
name,
server_ssl_enabled=False,
ssl_pass_through=False,
template='TCP',
insert_xff=False,
persist=False,
persist_method='cookie',
persist_cookie_name='JSESSIONID',
persist_cookie_mode='insert',
persist_expire=30):
self.payload = {
'name': name,
'serverSslEnabled': server_ssl_enabled,
'sslPassthrough': ssl_pass_through,
'template': template,
'insertXForwardedFor': insert_xff}
if persist:
self.payload['persistence'] = {
'method': persist_method,
'expire': persist_expire
}
if persist_cookie_mode == 'cookie':
self.payload['persistence']['cookieMode'] = persist_cookie_mode
self.payload['persistence']['cookieName'] = persist_cookie_name
def set_persistence(
self,
persist=False,
persist_method='cookie',
persist_cookie_name='JSESSIONID',
persist_cookie_mode='insert',
persist_expire=30):
if persist:
self.payload['persistence'] = {
'method': persist_method,
'expire': persist_expire
}
if persist_cookie_mode == 'cookie':
self.payload['persistence']['cookieMode'] = persist_cookie_mode
self.payload['persistence']['cookieName'] = persist_cookie_name
else:
self.payload.pop('persistence', None)
class NsxvLBAppRule(object):
def __init__(self, name, script):
self.payload = {
'name': name,
'script': script}
class NsxvLBVirtualServer(object):
def __init__(
self,
name,
ip_address,
port=80,
protocol='HTTP',
enabled=True,
acceleration_enabled=False,
connection_limit=0,
enable_service_insertion=False):
self.payload = {
'name': name,
'ipAddress': ip_address,
'port': port,
'protocol': protocol,
'enabled': enabled,
'accelerationEnabled': acceleration_enabled,
'connectionLimit': connection_limit,
'enableServiceInsertion': enable_service_insertion}
self.app_rules = {}
self.app_profile = None
self.default_pool = None
def add_app_rule(self, app_rule):
self.app_rules[app_rule.payload['name']] = app_rule
def del_app_rule(self, name):
self.app_rules.pop(name, None)
def set_default_pool(self, pool):
self.default_pool = pool
def set_app_profile(self, app_profile):
self.app_profile = app_profile
class NsxvLBMonitor(object):
def __init__(
self,
name,
interval=10,
max_retries=3,
method='GET',
timeout=15,
mon_type='http',
url='/'):
self.payload = {
'name': name,
'interval': interval,
'maxRetries': max_retries,
'method': method,
'timeout': timeout,
'type': mon_type,
'url': url}
class NsxvLBPoolMember(object):
def __init__(
self,
name,
ip_address,
port,
monitor_port=None,
condition='enabled',
weight=1,
min_conn=0,
max_conn=0):
self.payload = {
'name': name,
'ipAddress': ip_address,
'port': port,
'monitorPort': monitor_port,
'condition': condition,
'weight': weight,
'minConn': min_conn,
'maxConn': max_conn}
class NsxvLBPool(object):
def __init__(
self,
name,
algorithm='round-robin',
transparent=False):
self.payload = {
'name': name,
'algorithm': algorithm,
'transparent': transparent}
self.members = {}
self.monitors = {}
def add_member(self, member):
self.members[member.payload['name']] = member
def del_member(self, name):
self.members.pop(name, None)
def add_monitor(self, monitor):
self.monitors[monitor.payload['name']] = monitor
def del_monitor(self, name):
self.monitors.pop(name, None)

View File

@ -12,6 +12,7 @@ iso8601>=0.1.9
six>=1.7.0
oslo.i18n>=1.3.0 # Apache-2.0
oslo.serialization>=1.2.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0
Babel>=1.3
@ -20,5 +21,7 @@ PyYAML>=3.1.0
suds-jurko>=0.6
eventlet>=0.16.1
httplib2>=0.7.5
requests>=2.2.0,!=2.4.0
urllib3>=1.8.3
oslo.concurrency>=0.3.0,!=0.4.0 # Apache-2.0

View File

@ -14,6 +14,7 @@ iso8601>=0.1.9
six>=1.7.0
oslo.i18n>=1.3.0 # Apache-2.0
oslo.serialization>=1.2.0 # Apache-2.0
oslo.utils>=1.2.0 # Apache-2.0
Babel>=1.3
@ -22,6 +23,7 @@ PyYAML>=3.1.0
suds>=0.4
eventlet>=0.16.1
httplib2>=0.7.5
requests>=2.2.0,!=2.4.0
urllib3>=1.8.3
oslo.concurrency>=1.4.1 # Apache-2.0

View File

View File

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,117 @@
# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
# 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 mock
from oslo_vmware.network.nsx.nsxv.api import api_helper
from oslo_vmware.network.nsx.nsxv.common import exceptions
from tests import base
class NsxvApiHelperTestCase(base.TestCase):
def setUp(self):
super(NsxvApiHelperTestCase, self).setUp()
def test_get_success(self):
helper = api_helper.NsxvApiHelper(
'http://10.0.0.1',
'testuser',
'testpass')
h = {'status': '200',
'connection': 'keep-alive',
'content-type': 'text/html; charset=utf-8'}
v = '<html></html>'
with mock.patch.object(helper, '_http_request',
return_value=(h, v)) as mock_request:
helper.request('GET', '/test/index.html')
mock_request.assert_has_calls([
mock.call().request(
'http://10.0.0.1/test/index.html', 'GET', body=None,
headers={'Content-Type': 'application/json',
'Authorization':
'Basic dGVzdHVzZXI6dGVzdHBhc3M=',
'Accept': ('application/json',)})])
def test_get_fail(self):
helper = api_helper.NsxvApiHelper(
'http://10.0.0.1',
'testuser',
'testpass')
h = {'status': '404',
'connection': 'keep-alive',
'content-type': 'text/html; charset=utf-8'}
v = '<html></html>'
with mock.patch.object(helper, '_http_request',
return_value=(h, v)):
self.assertRaises(
exceptions.ResourceNotFound,
helper.request, 'GET', '/test/index.html')
def test_put_json(self):
helper = api_helper.NsxvApiHelper(
'http://10.0.0.1',
'testuser',
'testpass',
'json')
h = {'status': '201',
'connection': 'keep-alive',
'content-type': 'application/json'}
v = '<html></html>'
obj = {'test1': 'testing123'}
with mock.patch.object(helper, '_http_request',
return_value=(h, v)) as mock_request:
helper.request('PUT', '/test/index.html', obj)
mock_request.assert_has_calls([
mock.call().request(
'http://10.0.0.1/test/index.html', 'PUT',
body='{"test1": "testing123"}',
headers={
'Content-Type': 'application/json',
'Authorization': 'Basic dGVzdHVzZXI6dGVzdHBhc3M=',
'Accept': ('application/json',)})])
def test_put_xml(self):
helper = api_helper.NsxvApiHelper(
'http://10.0.0.1',
'testuser',
'testpass',
'xml')
h = {'status': '201',
'connection': 'keep-alive',
'content-type': 'application/xml'}
v = '<html></html>'
obj = {'test1': 'testing123'}
with mock.patch.object(helper, '_http_request',
return_value=(h, v)) as mock_request:
helper.request('PUT', '/test/index.html', obj)
mock_request.assert_has_calls([
mock.call().request(
'http://10.0.0.1/test/index.html', 'PUT',
body='<test1>testing123</test1>',
headers={
'Content-Type': 'application/xml',
'Authorization': 'Basic dGVzdHVzZXI6dGVzdHBhc3M=',
'Accept': ('application/xml',)})])

View File

@ -0,0 +1,98 @@
# Copyright 2015 VMware, Inc.
# All Rights Reserved
#
# 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 json
import mock
from oslo_vmware.network.nsx.nsxv.api import api as nsxv_api
from oslo_vmware.network.nsx.nsxv.objects import loadbalancer as nsxv_lb
from tests import base
class NsxvLoadbalancerTestCase(base.TestCase):
EDGE_OBJ_JSON = (
'{"accelerationEnabled":false,"applicationProfile":[{'
'"applicationProfileId":"applicationProfile-1","insertXForwardedFor":'
'false,"name":"MDSrvProxy","persistence":{"cookieMode":"insert",'
'"cookieName":"JSESSIONID","expire":"30","method":"cookie"},'
'"serverSslEnabled":false,"sslPassthrough":false,"template":"HTTP"}],'
'"applicationRule":[],"enableServiceInsertion":false,"enabled":true,'
'"featureType":"loadbalancer_4.0","logging":{"enable":false,'
'"logLevel":"info"},"monitor":[{"interval":10,"maxRetries":3,"method":'
'"GET","monitorId":"monitor-1","name":"MDSrvMon","timeout":15,"type":'
'"http","url":"/"}],"pool":[{"algorithm":"round-robin",'
'"applicationRuleId":[],"member":[{"condition":"enabled","ipAddress":'
'"192.168.0.39","maxConn":0,"memberId":"member-1","minConn":0,'
'"monitorPort":8775,"name":"Member-1","port":8775,"weight":1}],'
'"monitorId":["monitor-1"],"name":"MDSrvPool","poolId":"pool-1",'
'"transparent":false}],"version":6,"virtualServer":[{'
'"accelerationEnabled":false,"applicationProfileId":'
'"applicationProfile-1","applicationRuleId":[],"connectionLimit":0,'
'"defaultPoolId":"pool-1","enableServiceInsertion":false,'
'"enabled":true,"ipAddress":"169.254.0.3","name":"MdSrv",'
'"port":"8775","protocol":"http","virtualServerId":'
'"virtualServer-1"}]}')
OUT_OBJ_JSON = (
'{"accelerationEnabled": false, "applicationProfile": [{'
'"applicationProfileId": "applicationProfile-1", '
'"insertXForwardedFor": false, "name": "MDSrvProxy", "persistence": '
'{"expire": "30", "method": "cookie"}, "serverSslEnabled": false, '
'"sslPassthrough": false, "template": "HTTP"}],'
' "enableServiceInsertion": false, "enabled": true, "featureType": '
'"loadbalancer_4.0", "monitor": [{"interval": 10, "maxRetries": 3, '
'"method": "GET", "monitorId": "monitor-1", "name": "MDSrvMon", '
'"timeout": 15, "type": "http", "url": "/"}], "pool": [{"algorithm":'
' "round-robin", "member": [{"condition": "enabled", "ipAddress": '
'"192.168.0.39", "maxConn": 0, "memberId": "member-1", "minConn": 0, '
'"monitorPort": 8775, "name": "Member-1", "port": 8775, "weight": 1}],'
' "monitorId": ["monitor-1"], "name": "MDSrvPool", "poolId": "pool-1",'
' "transparent": false}], "virtualServer": [{"accelerationEnabled": '
'false, "applicationProfileId": "applicationProfile-1", '
'"connectionLimit": 0, "defaultPoolId": "pool-1", '
'"enableServiceInsertion": false, "enabled": true, "ipAddress": '
'"169.254.0.3", "name": "MdSrv", "port": "8775", "protocol": '
'"http", "virtualServerId": "virtualServer-1"}]}')
LB_URI = '/api/4.0/edges/%s/loadbalancer/config?async=true'
EDGE_1 = 'edge-x'
EDGE_2 = 'edge-y'
def setUp(self):
super(NsxvLoadbalancerTestCase, self).setUp()
self._lb = nsxv_lb.NsxvLoadbalancer()
self._nsxv = nsxv_api.NsxvApi(None, None, None, None)
def test_edge_loadbalancer(self):
h = None
v = json.loads(self.EDGE_OBJ_JSON)
with mock.patch.object(self._nsxv, 'do_request',
return_value=(h, v)) as mock_do_request:
# Retrieve Edge LB
lb = nsxv_lb.NsxvLoadbalancer.get_loadbalancer(
self._nsxv, self.EDGE_1)
# Repost Edge LB
lb.submit_to_backend(self._nsxv, self.EDGE_2)
mock_do_request.assert_called_with(
nsxv_api.HTTP_PUT,
self.LB_URI % self.EDGE_2,
self.OUT_OBJ_JSON,
format='json',
encode=False)