cc8ac92f4c
Change-Id: I88fb83f0cb531794b21a06cabb1f56c54744a0e1
238 lines
10 KiB
Python
238 lines
10 KiB
Python
# Copyright 2016 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 netaddr
|
|
|
|
from oslo_log import log as logging
|
|
|
|
from neutron.ipam import exceptions as ipam_exc
|
|
from neutron.ipam import requests as ipam_req
|
|
|
|
from vmware_nsx._i18n import _
|
|
from vmware_nsx.services.ipam.common import driver as common
|
|
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
|
|
from vmware_nsxlib.v3 import nsx_constants as error
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class Nsxv3IpamDriver(common.NsxAbstractIpamDriver):
|
|
"""IPAM Driver For NSX-V3 external & provider networks."""
|
|
|
|
def __init__(self, subnetpool, context):
|
|
super(Nsxv3IpamDriver, self).__init__(subnetpool, context)
|
|
self.nsxlib_ipam = self.get_core_plugin().nsxlib.ip_pool
|
|
|
|
# Mark which updates to the pool are supported
|
|
self.support_update_gateway = True
|
|
self.support_update_pools = True
|
|
|
|
@property
|
|
def _subnet_class(self):
|
|
return Nsxv3IpamSubnet
|
|
|
|
def _get_cidr_from_request(self, subnet_request):
|
|
return "%s/%s" % (subnet_request.subnet_cidr[0],
|
|
subnet_request.prefixlen)
|
|
|
|
def _get_ranges_from_request(self, subnet_request):
|
|
if subnet_request.allocation_pools:
|
|
ranges = [
|
|
{'start': str(pool[0]), 'end': str(pool[-1])}
|
|
for pool in subnet_request.allocation_pools]
|
|
else:
|
|
ranges = []
|
|
return ranges
|
|
|
|
def allocate_backend_pool(self, subnet_request):
|
|
"""Create a pool on the NSX backend and return its ID"""
|
|
# name/description length on backend is long, so there is no problem
|
|
name = 'subnet_' + subnet_request.subnet_id
|
|
description = 'OS IP pool for subnet ' + subnet_request.subnet_id
|
|
try:
|
|
response = self.nsxlib_ipam.create(
|
|
self._get_cidr_from_request(subnet_request),
|
|
allocation_ranges=self._get_ranges_from_request(
|
|
subnet_request),
|
|
display_name=name,
|
|
description=description,
|
|
gateway_ip=subnet_request.gateway_ip)
|
|
nsx_pool_id = response['id']
|
|
except Exception as e:
|
|
#TODO(asarfaty): handle specific errors
|
|
msg = _('Failed to create subnet IPAM: %s') % e
|
|
raise ipam_exc.IpamValueInvalid(message=msg)
|
|
return nsx_pool_id
|
|
|
|
def delete_backend_pool(self, nsx_pool_id):
|
|
# Because of the delete_subnet flow in the neutron plugin,
|
|
# some ports still hold IPs from this pool.
|
|
# Those ports be deleted shortly after this function.
|
|
# We need to release those IPs before deleting the backed pool,
|
|
# or else it will fail.
|
|
pool_allocations = self.nsxlib_ipam.get_allocations(nsx_pool_id)
|
|
if pool_allocations and pool_allocations.get('result_count'):
|
|
for allocation in pool_allocations.get('results', []):
|
|
ip_addr = allocation.get('allocation_id')
|
|
try:
|
|
self.nsxlib_ipam.release(nsx_pool_id, ip_addr)
|
|
except Exception as e:
|
|
LOG.warning("Failed to release ip %(ip)s from pool "
|
|
"%(pool)s: %(e)s",
|
|
{'ip': ip_addr, 'pool': nsx_pool_id, 'e': e})
|
|
try:
|
|
self.nsxlib_ipam.delete(nsx_pool_id)
|
|
except Exception as e:
|
|
LOG.error("Failed to delete IPAM from backend: %s", e)
|
|
# Continue anyway, since this subnet was already removed
|
|
|
|
def update_backend_pool(self, nsx_pool_id, subnet_request):
|
|
update_args = {
|
|
'cidr': self._get_cidr_from_request(subnet_request),
|
|
'allocation_ranges': self._get_ranges_from_request(subnet_request),
|
|
'gateway_ip': subnet_request.gateway_ip}
|
|
try:
|
|
self.nsxlib_ipam.update(
|
|
nsx_pool_id, **update_args)
|
|
except nsx_lib_exc.ManagerError as e:
|
|
LOG.error("NSX IPAM failed to update pool %(id)s: "
|
|
" %(e)s; code %(code)s",
|
|
{'e': e,
|
|
'id': nsx_pool_id,
|
|
'code': e.error_code})
|
|
if (e.error_code == error.ERR_CODE_IPAM_RANGE_MODIFY or
|
|
e.error_code == error.ERR_CODE_IPAM_RANGE_DELETE or
|
|
e.error_code == error.ERR_CODE_IPAM_RANGE_SHRUNK):
|
|
# The change is not allowed: already allocated IPs out of
|
|
# the new range
|
|
raise ipam_exc.InvalidSubnetRequest(
|
|
reason=_("Already allocated IPs outside of the updated "
|
|
"pools"))
|
|
except Exception as e:
|
|
# unexpected error
|
|
msg = _('Failed to update subnet IPAM: %s') % e
|
|
raise ipam_exc.IpamValueInvalid(message=msg)
|
|
|
|
|
|
class Nsxv3IpamSubnet(common.NsxAbstractIpamSubnet):
|
|
"""Manage IP addresses for the NSX V3 IPAM driver."""
|
|
|
|
def __init__(self, subnet_id, nsx_pool_id, ctx, tenant_id):
|
|
super(Nsxv3IpamSubnet, self).__init__(
|
|
subnet_id, nsx_pool_id, ctx, tenant_id)
|
|
self.nsxlib_ipam = self.get_core_plugin().nsxlib.ip_pool
|
|
|
|
def backend_allocate(self, address_request):
|
|
try:
|
|
# allocate a specific IP
|
|
if isinstance(address_request, ipam_req.SpecificAddressRequest):
|
|
# This handles both specific and automatic address requests
|
|
ip_address = str(address_request.address)
|
|
# If this is the subnet gateway IP - no need to allocate it
|
|
subnet = self.get_details()
|
|
if str(subnet.gateway_ip) == ip_address:
|
|
LOG.info("Skip allocation of gateway-ip for pool %s",
|
|
self._nsx_pool_id)
|
|
return ip_address
|
|
else:
|
|
# Allocate any free IP
|
|
ip_address = None
|
|
response = self.nsxlib_ipam.allocate(self._nsx_pool_id,
|
|
ip_addr=ip_address)
|
|
ip_address = response['allocation_id']
|
|
except nsx_lib_exc.ManagerError as e:
|
|
LOG.error("NSX IPAM failed to allocate ip %(ip)s of subnet "
|
|
"%(id)s: %(e)s; code %(code)s",
|
|
{'e': e,
|
|
'ip': ip_address,
|
|
'id': self._subnet_id,
|
|
'code': e.error_code})
|
|
if e.error_code == error.ERR_CODE_IPAM_POOL_EXHAUSTED:
|
|
# No more IP addresses available on the pool
|
|
raise ipam_exc.IpAddressGenerationFailure(
|
|
subnet_id=self._subnet_id)
|
|
if e.error_code == error.ERR_CODE_IPAM_SPECIFIC_IP:
|
|
# The NSX backend does not support allocation of specific IPs
|
|
# prior to version 2.0.
|
|
msg = (_("NSX-V3 IPAM driver does not support allocation of a "
|
|
"specific ip %s for port") % ip_address)
|
|
raise NotImplementedError(msg)
|
|
if e.error_code == error.ERR_CODE_IPAM_IP_ALLOCATED:
|
|
# This IP is already in use
|
|
raise ipam_exc.IpAddressAlreadyAllocated(
|
|
ip=ip_address, subnet_id=self._subnet_id)
|
|
if e.error_code == error.ERR_CODE_OBJECT_NOT_FOUND:
|
|
msg = (_("NSX-V3 IPAM failed to allocate: pool %s was not "
|
|
"found") % self._nsx_pool_id)
|
|
raise ipam_exc.IpamValueInvalid(message=msg)
|
|
else:
|
|
# another backend error
|
|
raise ipam_exc.IPAllocationFailed()
|
|
except Exception as e:
|
|
LOG.error("NSX IPAM failed to allocate ip %(ip)s of subnet "
|
|
"%(id)s: %(e)s",
|
|
{'e': e,
|
|
'ip': ip_address,
|
|
'id': self._subnet_id})
|
|
# handle unexpected failures
|
|
raise ipam_exc.IPAllocationFailed()
|
|
return ip_address
|
|
|
|
def backend_deallocate(self, ip_address):
|
|
# If this is the subnet gateway IP - no need to allocate it
|
|
subnet = self.get_details()
|
|
if str(subnet.gateway_ip) == ip_address:
|
|
LOG.info("Skip deallocation of gateway-ip for pool %s",
|
|
self._nsx_pool_id)
|
|
return
|
|
try:
|
|
self.nsxlib_ipam.release(self._nsx_pool_id, ip_address)
|
|
except nsx_lib_exc.ManagerError as e:
|
|
# fail silently
|
|
LOG.error("NSX IPAM failed to free ip %(ip)s of subnet "
|
|
"%(id)s: %(e)s; code %(code)s",
|
|
{'e': e,
|
|
'ip': ip_address,
|
|
'id': self._subnet_id,
|
|
'code': e.error_code})
|
|
|
|
def get_details(self):
|
|
"""Return subnet data as a SpecificSubnetRequest"""
|
|
# get the pool from the backend
|
|
try:
|
|
pool_details = self.nsxlib_ipam.get(self._nsx_pool_id)
|
|
except Exception as e:
|
|
msg = _('Failed to get details for nsx pool: %(id)s: '
|
|
'%(e)s') % {'id': self._nsx_pool_id, 'e': e}
|
|
raise ipam_exc.IpamValueInvalid(message=msg)
|
|
|
|
first_range = pool_details.get('subnets', [None])[0]
|
|
if not first_range:
|
|
msg = _('Failed to get details for nsx pool: %(id)s') % {
|
|
'id': self._nsx_pool_id}
|
|
raise ipam_exc.IpamValueInvalid(message=msg)
|
|
|
|
cidr = first_range.get('cidr')
|
|
gateway_ip = first_range.get('gateway_ip')
|
|
pools = []
|
|
for subnet in pool_details.get('subnets', []):
|
|
for ip_range in subnet.get('allocation_ranges', []):
|
|
pools.append(netaddr.IPRange(ip_range.get('start'),
|
|
ip_range.get('end')))
|
|
return ipam_req.SpecificSubnetRequest(
|
|
self._tenant_id, self._subnet_id,
|
|
cidr, gateway_ip=gateway_ip, allocation_pools=pools)
|