From 3356e2e147043a4e57117a5977932232d3412c52 Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Tue, 13 Jan 2015 09:01:03 +0200 Subject: [PATCH] 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 --- oslo_vmware/network/__init__.py | 0 oslo_vmware/network/nsx/__init__.py | 0 oslo_vmware/network/nsx/nsxv/__init__.py | 0 oslo_vmware/network/nsx/nsxv/api/__init__.py | 0 oslo_vmware/network/nsx/nsxv/api/api.py | 598 ++++++++ .../network/nsx/nsxv/api/api_helper.py | 117 ++ .../network/nsx/nsxv/common/__init__.py | 0 .../network/nsx/nsxv/common/exceptions.py | 97 ++ .../network/nsx/nsxv/objects/__init__.py | 0 .../network/nsx/nsxv/objects/edge_cfg_obj.py | 67 + .../network/nsx/nsxv/objects/loadbalancer.py | 392 ++++++ requirements-py3.txt | 3 + requirements.txt | 2 + tests/network/__init__.py | 0 tests/network/nsx/__init__.py | 0 tests/network/nsx/nsxv/__init__.py | 0 tests/network/nsx/nsxv/test_nsxv_api.py | 1216 +++++++++++++++++ .../network/nsx/nsxv/test_nsxv_api_helper.py | 117 ++ .../nsx/nsxv/test_nsxv_loadbalancer.py | 98 ++ 19 files changed, 2707 insertions(+) create mode 100644 oslo_vmware/network/__init__.py create mode 100644 oslo_vmware/network/nsx/__init__.py create mode 100644 oslo_vmware/network/nsx/nsxv/__init__.py create mode 100644 oslo_vmware/network/nsx/nsxv/api/__init__.py create mode 100644 oslo_vmware/network/nsx/nsxv/api/api.py create mode 100644 oslo_vmware/network/nsx/nsxv/api/api_helper.py create mode 100644 oslo_vmware/network/nsx/nsxv/common/__init__.py create mode 100644 oslo_vmware/network/nsx/nsxv/common/exceptions.py create mode 100644 oslo_vmware/network/nsx/nsxv/objects/__init__.py create mode 100644 oslo_vmware/network/nsx/nsxv/objects/edge_cfg_obj.py create mode 100644 oslo_vmware/network/nsx/nsxv/objects/loadbalancer.py create mode 100644 tests/network/__init__.py create mode 100644 tests/network/nsx/__init__.py create mode 100644 tests/network/nsx/nsxv/__init__.py create mode 100644 tests/network/nsx/nsxv/test_nsxv_api.py create mode 100644 tests/network/nsx/nsxv/test_nsxv_api_helper.py create mode 100644 tests/network/nsx/nsxv/test_nsxv_loadbalancer.py diff --git a/oslo_vmware/network/__init__.py b/oslo_vmware/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oslo_vmware/network/nsx/__init__.py b/oslo_vmware/network/nsx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oslo_vmware/network/nsx/nsxv/__init__.py b/oslo_vmware/network/nsx/nsxv/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oslo_vmware/network/nsx/nsxv/api/__init__.py b/oslo_vmware/network/nsx/nsxv/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oslo_vmware/network/nsx/nsxv/api/api.py b/oslo_vmware/network/nsx/nsxv/api/api.py new file mode 100644 index 00000000..ffa220b8 --- /dev/null +++ b/oslo_vmware/network/nsx/nsxv/api/api.py @@ -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 diff --git a/oslo_vmware/network/nsx/nsxv/api/api_helper.py b/oslo_vmware/network/nsx/nsxv/api/api_helper.py new file mode 100644 index 00000000..3ea33150 --- /dev/null +++ b/oslo_vmware/network/nsx/nsxv/api/api_helper.py @@ -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" % (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) diff --git a/oslo_vmware/network/nsx/nsxv/common/__init__.py b/oslo_vmware/network/nsx/nsxv/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oslo_vmware/network/nsx/nsxv/common/exceptions.py b/oslo_vmware/network/nsx/nsxv/common/exceptions.py new file mode 100644 index 00000000..aa517f97 --- /dev/null +++ b/oslo_vmware/network/nsx/nsxv/common/exceptions.py @@ -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") diff --git a/oslo_vmware/network/nsx/nsxv/objects/__init__.py b/oslo_vmware/network/nsx/nsxv/objects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oslo_vmware/network/nsx/nsxv/objects/edge_cfg_obj.py b/oslo_vmware/network/nsx/nsxv/objects/edge_cfg_obj.py new file mode 100644 index 00000000..95bc3e00 --- /dev/null +++ b/oslo_vmware/network/nsx/nsxv/objects/edge_cfg_obj.py @@ -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) diff --git a/oslo_vmware/network/nsx/nsxv/objects/loadbalancer.py b/oslo_vmware/network/nsx/nsxv/objects/loadbalancer.py new file mode 100644 index 00000000..5feb5120 --- /dev/null +++ b/oslo_vmware/network/nsx/nsxv/objects/loadbalancer.py @@ -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) diff --git a/requirements-py3.txt b/requirements-py3.txt index 8988bcbb..90c97353 100644 --- a/requirements-py3.txt +++ b/requirements-py3.txt @@ -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 diff --git a/requirements.txt b/requirements.txt index dfb723a6..cbaee504 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tests/network/__init__.py b/tests/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/network/nsx/__init__.py b/tests/network/nsx/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/network/nsx/nsxv/__init__.py b/tests/network/nsx/nsxv/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/network/nsx/nsxv/test_nsxv_api.py b/tests/network/nsx/nsxv/test_nsxv_api.py new file mode 100644 index 00000000..5bd516bc --- /dev/null +++ b/tests/network/nsx/nsxv/test_nsxv_api.py @@ -0,0 +1,1216 @@ +# 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 +from oslo_vmware.network.nsx.nsxv.api import api_helper +from oslo_vmware.network.nsx.nsxv.common import exceptions +from tests import base + + +class NsxvApiTestCase(base.TestCase): + + def setUp(self): + super(NsxvApiTestCase, self).setUp() + self.nsxv_api = api.NsxvApi('http://10.0.0.1', 'testuser', 'testpwd', + retries=2) + + def _test_helper(self, nsxv_api, http_method, uri, data, h, v, + retval, decode, headers, *args): + with mock.patch.object(api_helper.NsxvApiHelper, 'request', + return_value=(h, v)) as mock_request: + + h1, v1 = nsxv_api(*args) + mock_request.assert_has_calls([ + mock.call().request(http_method, uri, + data, headers, decode)]) + + self.assertEqual(h, h1) + if retval is not None: + self.assertEqual(retval, v1) + else: + self.assertEqual(v, v1) + + def test_deploy_edge(self): + h = {'status': '200', 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = [{'test1': 'test123'}] + self._test_helper( + self.nsxv_api.deploy_edge, + 'POST', + '/api/4.0/edges?async=true', + json.loads(v), + h, v, None, True, None, *args) + + def test_update_edge(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.update_edge, + 'PUT', + '/api/4.0/edges/edge-x?async=true', + json.loads(v), + h, v, None, True, None, *args) + + def test_get_edge_id(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['jobdata-15779'] + self._test_helper( + self.nsxv_api.get_edge_id, + 'GET', + '/api/4.0/edges/jobs/jobdata-15779', + None, h, v, json.loads(v), True, None, *args) + + def test_get_edge_jobs(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_edge_jobs, + 'GET', + '/api/4.0/edges/edge-x/jobs', + None, h, v, json.loads(v), True, None, *args) + + def test_get_edge_deploy_status(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_edge_deploy_status, + 'GET', + '/api/4.0/edges/edge-x/status?getlatest=false', + None, h, v, json.loads(v), True, None, *args) + + def test_delete_edge(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.delete_edge, + 'DELETE', + '/api/4.0/edges/edge-x', + None, h, v, json.loads(v), True, None, *args) + + def test_add_vdr_internal_interface(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 1] + self._test_helper( + self.nsxv_api.add_vdr_internal_interface, + 'POST', + '/api/4.0/edges/edge-x/interfaces?action=patch&async=true', + 1, h, v, json.loads(v), True, None, *args) + + def test_update_vdr_internal_interface(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 1, json.loads(v)] + self._test_helper( + self.nsxv_api.update_vdr_internal_interface, + 'PUT', + '/api/4.0/edges/edge-x/interfaces/1?async=true', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_vdr_internal_interface(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 1] + self._test_helper( + self.nsxv_api.delete_vdr_internal_interface, + 'DELETE', + '/api/4.0/edges/edge-x/interfaces/1?async=true', + None, h, v, json.loads(v), True, None, *args) + + def test_get_interfaces(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_interfaces, + 'GET', + '/api/4.0/edges/edge-x/vnics', + None, h, v, json.loads(v), True, None, *args) + + def test_update_interface(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"index": 1, "test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.update_interface, + 'PUT', + '/api/4.0/edges/edge-x/vnics/1?async=true', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_interface(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"index": 1, "test1": "test123"}' + + args = ['edge-x', 1] + self._test_helper( + self.nsxv_api.delete_interface, + 'DELETE', + '/api/4.0/edges/edge-x/vnics/1?async=true', + None, h, v, json.loads(v), True, None, *args) + + def test_get_nat_config(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_nat_config, + 'GET', + '/api/4.0/edges/edge-x/nat/config', + None, h, v, json.loads(v), True, None, *args) + + def test_update_nat_config(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.update_nat_config, + 'PUT', + '/api/4.0/edges/edge-x/nat/config?async=true', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_nat_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 1] + self._test_helper( + self.nsxv_api.delete_nat_rule, + 'DELETE', + '/api/4.0/edges/edge-x/nat/config/rules/1', + None, h, v, json.loads(v), True, None, *args) + + def test_get_edge_status(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_edge_status, + 'GET', + '/api/4.0/edges/edge-x/status?getlatest=false', + None, h, v, json.loads(v), True, None, *args) + + def test_get_edges(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = [] + self._test_helper( + self.nsxv_api.get_edges, + 'GET', + '/api/4.0/edges', + None, h, v, json.loads(v), True, None, *args) + + def test_get_edge_interfaces(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_edge_interfaces, + 'GET', + '/api/4.0/edges/edge-x/interfaces', + None, h, v, json.loads(v), True, None, *args) + + def test_update_routes(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.update_routes, + 'PUT', + '/api/4.0/edges/edge-x/routing/config/static?async=true', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_create_lswitch(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = [json.loads(v)] + self._test_helper( + self.nsxv_api.create_lswitch, + 'POST', + '/api/ws.v1/lswitch', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_lswitch(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = [123] + self._test_helper( + self.nsxv_api.delete_lswitch, + 'DELETE', + '/api/ws.v1/lswitch/123', + None, h, v, json.loads(v), True, None, *args) + + def test_get_loadbalancer_config(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_loadbalancer_config, + 'GET', + '/api/4.0/edges/edge-x/loadbalancer/config', + None, h, v, json.loads(v), True, None, *args) + + def test_enable_service_loadbalancer(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.enable_service_loadbalancer, + 'PUT', + '/api/4.0/edges/edge-x/loadbalancer/config', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_update_firewall(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.update_firewall, + 'PUT', + '/api/4.0/edges/edge-x/firewall/config?async=true', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_firewall(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.delete_firewall, + 'DELETE', + '/api/4.0/edges/edge-x/firewall/config?async=true', + None, h, v, json.loads(v), True, None, *args) + + def test_update_firewall_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', '123', json.loads(v)] + self._test_helper( + self.nsxv_api.update_firewall_rule, + 'PUT', + '/api/4.0/edges/edge-x/firewall/config/rules/123', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_firewall_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', '123'] + self._test_helper( + self.nsxv_api.delete_firewall_rule, + 'DELETE', + '/api/4.0/edges/edge-x/firewall/config/rules/123', + None, h, v, json.loads(v), True, None, *args) + + def test_add_firewall_rule_above(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', '123', json.loads(v)] + self._test_helper( + self.nsxv_api.add_firewall_rule_above, + 'POST', + '/api/4.0/edges/edge-x/firewall/config/rules?aboveRuleId=123', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_add_firewall_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.add_firewall_rule, + 'POST', + '/api/4.0/edges/edge-x/firewall/config/rules', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_get_firewall(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_firewall, + 'GET', + '/api/4.0/edges/edge-x/firewall/config', + None, h, v, json.loads(v), True, None, *args) + + def test_get_firewall_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.get_firewall_rule, + 'GET', + '/api/4.0/edges/edge-x/firewall/config/rules/123', + None, h, v, json.loads(v), True, None, *args) + + def test_create_vip(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.create_vip, + 'POST', + '/api/4.0/edges/edge-x/loadbalancer/config/virtualservers', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_get_vip(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.get_vip, + 'GET', + '/api/4.0/edges/edge-x/loadbalancer/config/virtualservers/123', + None, h, v, json.loads(v), True, None, *args) + + def test_update_vip(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123, json.loads(v)] + self._test_helper( + self.nsxv_api.update_vip, + 'PUT', + '/api/4.0/edges/edge-x/loadbalancer/config/virtualservers/123', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_vip(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.delete_vip, + 'DELETE', + '/api/4.0/edges/edge-x/loadbalancer/config/virtualservers/123', + None, h, v, json.loads(v), True, None, *args) + + def test_create_pool(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.create_pool, + 'POST', + '/api/4.0/edges/edge-x/loadbalancer/config/pools', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_get_pool(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.get_pool, + 'GET', + '/api/4.0/edges/edge-x/loadbalancer/config/pools/123', + None, h, v, json.loads(v), True, None, *args) + + def test_update_pool(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123, json.loads(v)] + self._test_helper( + self.nsxv_api.update_pool, + 'PUT', + '/api/4.0/edges/edge-x/loadbalancer/config/pools/123', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_pool(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.delete_pool, + 'DELETE', + '/api/4.0/edges/edge-x/loadbalancer/config/pools/123', + None, h, v, json.loads(v), True, None, *args) + + def test_create_health_monitor(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.create_health_monitor, + 'POST', + '/api/4.0/edges/edge-x/loadbalancer/config/monitors', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_get_health_monitor(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.get_health_monitor, + 'GET', + '/api/4.0/edges/edge-x/loadbalancer/config/monitors/123', + None, h, v, json.loads(v), True, None, *args) + + def test_update_health_monitor(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123, json.loads(v)] + self._test_helper( + self.nsxv_api.update_health_monitor, + 'PUT', + '/api/4.0/edges/edge-x/loadbalancer/config/monitors/123', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_health_monitor(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.delete_health_monitor, + 'DELETE', + '/api/4.0/edges/edge-x/loadbalancer/config/monitors/123', + None, h, v, json.loads(v), True, None, *args) + + def test_create_app_profile(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.create_app_profile, + 'POST', + '/api/4.0/edges/edge-x/loadbalancer/config/applicationprofiles', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_update_app_profile(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 1, json.loads(v)] + self._test_helper( + self.nsxv_api.update_app_profile, + 'PUT', + '/api/4.0/edges/edge-x/loadbalancer/config/applicationprofiles/1', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_app_profile(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 1] + self._test_helper( + self.nsxv_api.delete_app_profile, + 'DELETE', + '/api/4.0/edges/edge-x/loadbalancer/config/applicationprofiles/1', + None, h, v, json.loads(v), True, None, *args) + + def test_create_app_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.create_app_rule, + 'POST', + '/api/4.0/edges/edge-x/loadbalancer/config/applicationrules', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_update_app_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123, json.loads(v)] + self._test_helper( + self.nsxv_api.update_app_rule, + 'PUT', + '/api/4.0/edges/edge-x/loadbalancer/config/applicationrules/123', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_app_rule(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.delete_app_rule, + 'DELETE', + '/api/4.0/edges/edge-x/loadbalancer/config/applicationrules/123', + None, h, v, json.loads(v), True, None, *args) + + def test_update_ipsec_config(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.update_ipsec_config, + 'PUT', + '/api/4.0/edges/edge-x/ipsec/config', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_delete_ipsec_config(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.delete_ipsec_config, + 'DELETE', + '/api/4.0/edges/edge-x/ipsec/config', + None, h, v, json.loads(v), True, None, *args) + + def test_get_ipsec_config(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.get_ipsec_config, + 'GET', + '/api/4.0/edges/edge-x/ipsec/config', + None, h, v, json.loads(v), True, None, *args) + + def test_create_virtual_wire(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = ['vdn-scope-x', {'test1': 'test123'}] + self._test_helper( + self.nsxv_api.create_virtual_wire, + 'POST', + '/api/2.0/vdn/scopes/vdn-scope-x/virtualwires', + {'test1': 'test123'}, h, v, None, True, None, *args) + + def test_delete_virtual_wire(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + + args = [123] + self._test_helper( + self.nsxv_api.delete_virtual_wire, + 'DELETE', + '/api/2.0/vdn/virtualwires/123', + None, h, '', {}, True, None, *args) + + def test_create_port_group(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = [123, {'test1': 'test123'}] + self._test_helper( + self.nsxv_api.create_port_group, + 'POST', + '/api/2.0/xvs/switches/123/networks', + {'test1': 'test123'}, h, v, None, True, None, *args) + + def test_delete_port_group(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + + args = [123, 456] + self._test_helper( + self.nsxv_api.delete_port_group, + 'DELETE', + '/api/2.0/xvs/switches/123/networks/456', + None, h, '', {}, True, None, *args) + + def test_query_interface(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', 123] + self._test_helper( + self.nsxv_api.query_interface, + 'GET', + '/api/4.0/edges/edge-x/vnics/123', + None, h, v, json.loads(v), True, None, *args) + + def test_reconfigure_dhcp_service(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.reconfigure_dhcp_service, + 'PUT', + '/api/4.0/edges/edge-x/dhcp/config?async=true', + json.loads(v), h, v, json.loads(v), True, None, *args) + + def test_query_dhcp_configuration(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x'] + self._test_helper( + self.nsxv_api.query_dhcp_configuration, + 'GET', + '/api/4.0/edges/edge-x/dhcp/config', + None, h, v, json.loads(v), True, None, *args) + + def test_create_dhcp_binding(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + v = '{"test1": "test123"}' + + args = ['edge-x', json.loads(v)] + self._test_helper( + self.nsxv_api.create_dhcp_binding, + 'POST', + '/api/4.0/edges/edge-x/dhcp/config/bindings?async=true', + json.loads(v), h, None, None, True, None, *args) + + def test_delete_dhcp_binding(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + + args = ['edge-x', 456] + self._test_helper( + self.nsxv_api.delete_dhcp_binding, + 'DELETE', + '/api/4.0/edges/edge-x/dhcp/config/bindings/456?async=true', + None, h, '', {}, True, None, *args) + + def test_create_security_group(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = [{'test1': 'test123'}] + self._test_helper( + self.nsxv_api.create_security_group, + 'POST', + '/api/2.0/services/securitygroup/globalroot-0', + {'test1': 'test123'}, h, v, None, True, None, *args) + + def test_delete_security_group(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'} + + args = [123] + self._test_helper( + self.nsxv_api.delete_security_group, + 'DELETE', + '/api/2.0/services/securitygroup/123?force=true', + None, h, '', {}, True, None, *args) + + def test_create_section_ip(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = ['ip', {'test1': 'test123'}] + self._test_helper( + self.nsxv_api.create_section, + 'POST', + '/api/4.0/firewall/globalroot-0/' + 'config/layer3sections?autoSaveDraft=false', + {'test1': 'test123'}, h, v, None, False, None, *args) + + def test_create_section_eth(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = ['eth', {'test1': 'test123'}] + self._test_helper( + self.nsxv_api.create_section, + 'POST', + '/api/4.0/firewall/globalroot-0/' + 'config/layer2sections?autoSaveDraft=false', + {'test1': 'test123'}, h, v, None, False, None, *args) + + def test_update_section(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = ['/api/4.0/firewall/globalroot-0/config/layer3sections/123', + {'test1': 'test123'}, {'etag': '1234'}] + self._test_helper( + self.nsxv_api.update_section, + 'PUT', + '/api/4.0/firewall/globalroot-0/config/' + 'layer3sections/123?autoSaveDraft=false', + {'test1': 'test123'}, h, v, None, False, {'If-Match': '1234'}, + *args) + + def test_delete_section(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + + args = ['/api/4.0/firewall/globalroot-0/config/layer3sections/123s'] + self._test_helper( + self.nsxv_api.delete_section, + 'DELETE', + '/api/4.0/firewall/globalroot-0/config/' + 'layer3sections/123s?autoSaveDraft=false', + None, h, '', {}, True, None, *args) + + def test_get_section(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = ['/api/4.0/firewall/globalroot-0/config/layer3sections/123'] + self._test_helper( + self.nsxv_api.get_section, + 'GET', + '/api/4.0/firewall/globalroot-0/config/layer3sections/123', + None, h, v, v, True, None, *args) + + def test_get_section_id(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = ('' + '' + '
' + '
' + '
' + '
') + + with mock.patch.object(api_helper.NsxvApiHelper, 'request', + return_value=(h, v)) as mock_request: + + ret = self.nsxv_api.get_section_id('test5') + mock_request.assert_has_calls([ + mock.call().request( + 'GET', + '/api/4.0/firewall/globalroot-0/config', + None, None, True)]) + + self.assertEqual(ret, '5') + + with mock.patch.object(api_helper.NsxvApiHelper, 'request', + return_value=(h, v)) as mock_request: + + ret = self.nsxv_api.get_section_id('test4') + mock_request.assert_has_calls([ + mock.call().request( + 'GET', + '/api/4.0/firewall/globalroot-0/config', + None, None, True)]) + + self.assertEqual(ret, '4') + + def _fake_request_for_test_update_section_by_id( + self, method, uri, params=None, headers=None, encodeparams=True): + # If this is the call from _get_section_header() act accordingly + v = '{"test1": "test123"}' + + if(method == 'GET' + and uri == ('/api/4.0/firewall/globalroot-0/config/' + 'layer3sections/test4') + and params is None and headers is None and encodeparams): + return {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json', + 'etag': '1234'}, '' + # That is the call from update_section_by_id() + elif(method == 'PUT' + and uri == ('/api/4.0/firewall/globalroot-0/config/' + 'layer3sections/test4?autoSaveDraft=false') + and headers == {'If-Match': '1234'} + and params == json.loads(v)): + return {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'}, '' + else: + self.fail() + + def test_update_section_by_id(self): + v = '{"test1": "test123"}' + + with mock.patch.object( + api_helper.NsxvApiHelper, 'request', + self._fake_request_for_test_update_section_by_id): + ret = self.nsxv_api.update_section_by_id( + 'test4', 'ip', json.loads(v)) + self.assertEqual(ret, None) + + def _fake_request_for_test_remove_rule_from_section( + self, method, uri, params=None, headers=None, encodeparams=True): + # If this is the call from _get_section_header() act accordingly + + if(method == 'GET' + and uri == ('/api/4.0/firewall/globalroot-0/config/' + 'layer3sections/123') + and params is None and headers is None and encodeparams): + return {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json', + 'etag': '1234'}, '' + # That is the call from remove_rule_from_section() + elif(method == 'DELETE' + and uri == ('/api/4.0/firewall/globalroot-0/config/layer3sections' + '/123/rules/1234?autoSaveDraft=false') + and headers == {'If-Match': '1234'} + and params is None): + return {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'}, '' + else: + self.fail() + + def test_remove_rule_from_section(self): + with mock.patch.object( + api_helper.NsxvApiHelper, 'request', + self._fake_request_for_test_remove_rule_from_section): + h, v = self.nsxv_api.remove_rule_from_section( + '/api/4.0/firewall/globalroot-0/config/layer3sections/123', + '1234') + self.assertEqual(h, {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'}) + self.assertEqual(v, {}) + + def test_add_member_to_security_group(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + v = 'test123' + + args = ['123', '456'] + self._test_helper( + self.nsxv_api.add_member_to_security_group, + 'PUT', + '/api/2.0/services/securitygroup/123/members/456', + None, h, v, None, True, None, *args) + + def test_remove_member_from_security_group(self): + h = {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/xml'} + + args = ['123', '456'] + self._test_helper( + self.nsxv_api.remove_member_from_security_group, + 'DELETE', + '/api/2.0/services/securitygroup/123/members/456', + None, h, None, None, True, None, *args) + + +class NsxvApiRetryTestCase(base.TestCase): + + def setUp(self): + super(NsxvApiRetryTestCase, self).setUp() + self.nsxv_api = api.NsxvApi('http://10.0.0.1', 'testuser', 'testpwd', + retries=2) + + def _fake_request_1_retry( + self, method, uri, params=None, headers=None, encodeparams=True): + """Throw exception on 1st call, succeed on second attempt.""" + if(method == 'POST' + and uri == '/api/4.0/edges?async=true' + and params == {'test1': 'test123'} + and headers is None and encodeparams): + if self._ctr == 1: + self._ctr += 1 + raise exceptions.ServiceConflict + elif self._ctr == 2: + return {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'}, '' + else: + self.fail() + + def test_deploy_edge_1_retry(self): + self._ctr = 1 + + with mock.patch.object( + api_helper.NsxvApiHelper, 'request', + self._fake_request_1_retry): + h, v = self.nsxv_api.deploy_edge({'test1': 'test123'}) + self.assertEqual(h, {'status': '200', + 'connection': 'keep-alive', + 'content-type': 'application/json'}) + self.assertEqual(v, {}) + + def _fake_request_2_retries( + self, method, uri, params=None, headers=None, encodeparams=True): + """Always fail, just valiadate header params.""" + if(method == 'POST' + and uri == '/api/4.0/edges?async=true' + and params == {'test1': 'test123'} + and headers is None and encodeparams): + raise exceptions.ServiceConflict + else: + self.fail() + + def test_deploy_edge_2_retries_fail(self): + self._ctr = 1 + + with mock.patch.object( + api_helper.NsxvApiHelper, 'request', + self._fake_request_2_retries): + self.assertRaises(exceptions.ServiceConflict, + self.nsxv_api.deploy_edge, {'test1': 'test123'}) + + +class NsxvConfigValidationTestCase(base.TestCase): + + SWITCHES_XML = ( + '' + 'dvs-15' + '') + + SCOPINGOBJECTS_XML = ( + '' + 'datacenter-2Datacenter' + 'network-23' + 'Network' + 'network-12Network' + '') + + VDNSCOPES_XML = ( + '' + 'vdnscope-1') + + @mock.patch.object(api_helper, 'NsxvApiHelper') + def setUp(self, _mock_client): + super(NsxvConfigValidationTestCase, self).setUp() + self._nsxv_api = api.NsxvApi(None, None, None) + + def test_validate_dvs_success(self): + h = None + v = self.SWITCHES_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertTrue(self._nsxv_api.validate_dvs('dvs-15')) + + def test_validate_dvs_fail(self): + h = None + v = self.SWITCHES_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertFalse(self._nsxv_api.validate_dvs('dvs-14')) + + def test_validate_datacenter_moid_success(self): + h = None + v = self.SCOPINGOBJECTS_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertTrue(self._nsxv_api.validate_datacenter_moid( + 'datacenter-2')) + + def test_validate_datacenter_moid_fail(self): + h = None + v = self.SCOPINGOBJECTS_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertFalse(self._nsxv_api.validate_datacenter_moid( + 'network-23')) + + def test_validate_network_success(self): + h = None + v = self.SCOPINGOBJECTS_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertTrue(self._nsxv_api.validate_network( + 'network-23')) + + def test_validate_network_fail(self): + h = None + v = self.SCOPINGOBJECTS_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertFalse(self._nsxv_api.validate_network( + 'network-24')) + + def test_validate_vdn_scope_success(self): + h = None + v = self.VDNSCOPES_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertTrue(self._nsxv_api.validate_vdn_scope( + 'vdnscope-1')) + + def test_validate_vdn_scope_fail(self): + h = None + v = self.VDNSCOPES_XML + + with mock.patch.object(self._nsxv_api, 'do_request', + return_value=(h, v)): + self.assertFalse(self._nsxv_api.validate_vdn_scope( + 'vdnscope-2')) diff --git a/tests/network/nsx/nsxv/test_nsxv_api_helper.py b/tests/network/nsx/nsxv/test_nsxv_api_helper.py new file mode 100644 index 00000000..c58207de --- /dev/null +++ b/tests/network/nsx/nsxv/test_nsxv_api_helper.py @@ -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 = '' + + 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 = '' + + 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 = '' + 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 = '' + 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='testing123', + headers={ + 'Content-Type': 'application/xml', + 'Authorization': 'Basic dGVzdHVzZXI6dGVzdHBhc3M=', + 'Accept': ('application/xml',)})]) diff --git a/tests/network/nsx/nsxv/test_nsxv_loadbalancer.py b/tests/network/nsx/nsxv/test_nsxv_loadbalancer.py new file mode 100644 index 00000000..b0f0b922 --- /dev/null +++ b/tests/network/nsx/nsxv/test_nsxv_loadbalancer.py @@ -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)