191 lines
7.4 KiB
Python
191 lines
7.4 KiB
Python
# 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.
|
|
from time import sleep
|
|
|
|
from kuryr.lib._i18n import _LE
|
|
from kuryr.lib import constants as kl_const
|
|
from kuryr.lib import segmentation_type_drivers as seg_driver
|
|
from neutronclient.common import exceptions as n_exc
|
|
from oslo_config import cfg as oslo_cfg
|
|
from oslo_log import log as logging
|
|
|
|
from kuryr_kubernetes import clients
|
|
from kuryr_kubernetes import config
|
|
from kuryr_kubernetes import constants as const
|
|
from kuryr_kubernetes.controller.drivers import generic_vif
|
|
from kuryr_kubernetes import exceptions as k_exc
|
|
from kuryr_kubernetes import os_vif_util as ovu
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
DEFAULT_MAX_RETRY_COUNT = 3
|
|
DEFAULT_RETRY_INTERVAL = 1
|
|
|
|
|
|
class NestedVlanPodVIFDriver(generic_vif.GenericPodVIFDriver):
|
|
"""Manages ports for nested-containers to provide VIFs."""
|
|
|
|
def request_vif(self, pod, project_id, subnets, security_groups):
|
|
neutron = clients.get_neutron_client()
|
|
parent_port = self._get_parent_port(neutron, pod)
|
|
trunk_id = self._get_trunk_id(parent_port)
|
|
|
|
rq = self._get_port_request(pod, project_id, subnets, security_groups)
|
|
port = neutron.create_port(rq).get('port')
|
|
|
|
vlan_id = self._add_subport(neutron, trunk_id, port['id'])
|
|
|
|
vif_plugin = const.K8S_OS_VIF_NOOP_PLUGIN
|
|
vif = ovu.neutron_to_osvif_vif(vif_plugin, port, subnets)
|
|
vif.vlan_id = vlan_id
|
|
return vif
|
|
|
|
def release_vif(self, pod, vif):
|
|
neutron = clients.get_neutron_client()
|
|
parent_port = self._get_parent_port(neutron, pod)
|
|
trunk_id = self._get_trunk_id(parent_port)
|
|
self._remove_subport(neutron, trunk_id, vif.id)
|
|
self._release_vlan_id(vif.vlan_id)
|
|
try:
|
|
neutron.delete_port(vif.id)
|
|
except n_exc.PortNotFoundClient:
|
|
LOG.debug('Unable to release port %s as it no longer exists.',
|
|
vif.id)
|
|
|
|
def _get_port_request(self, pod, project_id, subnets, security_groups):
|
|
port_req_body = {'project_id': project_id,
|
|
'name': self._get_port_name(pod),
|
|
'network_id': self._get_network_id(subnets),
|
|
'fixed_ips': ovu.osvif_to_neutron_fixed_ips(subnets),
|
|
'device_owner': kl_const.DEVICE_OWNER,
|
|
'admin_state_up': True}
|
|
|
|
if security_groups:
|
|
port_req_body['security_groups'] = security_groups
|
|
|
|
return {'port': port_req_body}
|
|
|
|
def _get_trunk_id(self, port):
|
|
try:
|
|
return port['trunk_details']['trunk_id']
|
|
except KeyError:
|
|
LOG.error(_LE("Neutron port is missing trunk details. "
|
|
"Please ensure that k8s node port is associated "
|
|
"with a Neutron vlan trunk"))
|
|
raise k_exc.K8sNodeTrunkPortFailure
|
|
|
|
def _get_parent_port(self, neutron, pod):
|
|
node_subnet_id = config.CONF.neutron_defaults.worker_nodes_subnet
|
|
if not node_subnet_id:
|
|
raise oslo_cfg.RequiredOptError('worker_nodes_subnet',
|
|
'neutron_defaults')
|
|
|
|
try:
|
|
# REVISIT(vikasc): Assumption is being made that hostIP is the IP
|
|
# of trunk interface on the node(vm).
|
|
node_fixed_ip = pod['status']['hostIP']
|
|
except KeyError:
|
|
if pod['status']['conditions'][0]['type'] != "Initialized":
|
|
LOG.debug("Pod condition type is not 'Initialized'")
|
|
|
|
LOG.error(_LE("Failed to get parent vm port ip"))
|
|
raise
|
|
|
|
try:
|
|
fixed_ips = ['subnet_id=%s' % str(node_subnet_id),
|
|
'ip_address=%s' % str(node_fixed_ip)]
|
|
ports = neutron.list_ports(fixed_ips=fixed_ips)
|
|
except n_exc.NeutronClientException as ex:
|
|
LOG.error(_LE("Parent vm port with fixed ips %s not found!"),
|
|
fixed_ips)
|
|
raise ex
|
|
|
|
if ports['ports']:
|
|
return ports['ports'][0]
|
|
else:
|
|
LOG.error(_LE("Neutron port for vm port with fixed ips %s"
|
|
" not found!"), fixed_ips)
|
|
raise k_exc.K8sNodeTrunkPortFailure
|
|
|
|
def _add_subport(self, neutron, trunk_id, subport):
|
|
"""Adds subport port to Neutron trunk
|
|
|
|
This method gets vlanid allocated from kuryr segmentation driver.
|
|
In active/active HA type deployment, possibility of vlanid conflict
|
|
is there. In such a case, vlanid will be requested again and subport
|
|
addition is re-tried. This is tried DEFAULT_MAX_RETRY_COUNT times in
|
|
case of vlanid conflict.
|
|
"""
|
|
# TODO(vikasc): Better approach for retrying in case of
|
|
# vlan-id conflict.
|
|
retry_count = 1
|
|
while True:
|
|
try:
|
|
vlan_id = self._get_vlan_id(trunk_id)
|
|
except n_exc.NeutronClientException as ex:
|
|
LOG.error(_LE("Getting VlanID for subport on "
|
|
"trunk %s failed!!"), trunk_id)
|
|
raise ex
|
|
subport = [{'segmentation_id': vlan_id,
|
|
'port_id': subport,
|
|
'segmentation_type': 'vlan'}]
|
|
try:
|
|
neutron.trunk_add_subports(trunk_id,
|
|
{'sub_ports': subport})
|
|
except n_exc.Conflict as ex:
|
|
if retry_count < DEFAULT_MAX_RETRY_COUNT:
|
|
LOG.error(_LE("vlanid already in use on trunk, "
|
|
"%s. Retrying..."), trunk_id)
|
|
retry_count += 1
|
|
sleep(DEFAULT_RETRY_INTERVAL)
|
|
continue
|
|
else:
|
|
LOG.error(_LE(
|
|
"MAX retry count reached. Failed to add subport"))
|
|
raise ex
|
|
|
|
except n_exc.NeutronClientException as ex:
|
|
LOG.error(_LE("Error happened during subport"
|
|
"addition to trunk, %s"), trunk_id)
|
|
raise ex
|
|
return vlan_id
|
|
|
|
def _remove_subport(self, neutron, trunk_id, subport_id):
|
|
subport_id = [{'port_id': subport_id}]
|
|
try:
|
|
neutron.trunk_remove_subports(trunk_id,
|
|
{'sub_ports': subport_id})
|
|
except n_exc.NeutronClientException as ex:
|
|
LOG.error(_LE(
|
|
"Error happened during subport removal from trunk,"
|
|
"%s"), trunk_id)
|
|
raise ex
|
|
|
|
def _get_vlan_id(self, trunk_id):
|
|
vlan_ids = self._get_in_use_vlan_ids_set(trunk_id)
|
|
return seg_driver.allocate_segmentation_id(vlan_ids)
|
|
|
|
def _release_vlan_id(self, id):
|
|
return seg_driver.release_segmentation_id(id)
|
|
|
|
def _get_in_use_vlan_ids_set(self, trunk_id):
|
|
vlan_ids = set()
|
|
neutron = clients.get_neutron_client()
|
|
trunk = neutron.show_trunk(trunk_id)
|
|
for port in trunk['trunk']['sub_ports']:
|
|
vlan_ids.add(port['segmentation_id'])
|
|
|
|
return vlan_ids
|