
Including all python modules for nova-zvm-virt-driver and neutron-zvm-plugin. Change-Id: I72dd9f64fc412cbf10f5e7ab6e4ac465a977e849
351 lines
14 KiB
Python
Executable File
351 lines
14 KiB
Python
Executable File
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2014 IBM Corp.
|
|
#
|
|
# 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 eventlet
|
|
import sys
|
|
import time
|
|
import zvm_network
|
|
|
|
from oslo.config import cfg
|
|
|
|
from neutron import context
|
|
from neutron.agent import rpc as agent_rpc
|
|
from neutron.common import config as common_config
|
|
from neutron.common import constants as q_const
|
|
from neutron.common import rpc as n_rpc
|
|
from neutron.common import topics
|
|
from neutron.plugins.common import constants as p_const
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.openstack.common import loopingcall
|
|
from neutron.openstack.common.gettextutils import _
|
|
from neutron.plugins.zvm.common import exception
|
|
from neutron.plugins.zvm.common import utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def restart_wrapper(func):
|
|
def wrapper(*args, **kw):
|
|
gen = func(*args, **kw)
|
|
gen.next()
|
|
return gen
|
|
return wrapper
|
|
|
|
|
|
class zvmNeutronAgent(n_rpc.RpcCallback):
|
|
RPC_API_VERSION = '1.1'
|
|
|
|
def __init__(self):
|
|
super(zvmNeutronAgent, self).__init__()
|
|
self._utils = utils.zvmUtils()
|
|
self._polling_interval = cfg.CONF.AGENT.polling_interval
|
|
self._zhcp_node = cfg.CONF.AGENT.xcat_zhcp_nodename
|
|
self._host = cfg.CONF.AGENT.zvm_host
|
|
|
|
zvm_net = zvm_network.zvmNetwork()
|
|
self.agent_state = {
|
|
'binary': 'neutron-zvm-agent',
|
|
'host': self._host,
|
|
'topic': q_const.L2_AGENT_TOPIC,
|
|
'configurations': {'vswitch_mappings': zvm_net.get_network_maps()},
|
|
'agent_type': q_const.AGENT_TYPE_ZVM,
|
|
'start_flag': True}
|
|
self._setup_server_rpc()
|
|
self._zhcp_userid = self._utils.get_zhcp_userid(self._zhcp_node)
|
|
self._restart_handler = self._handle_restart()
|
|
|
|
def _setup_server_rpc(self):
|
|
self.agent_id = 'zvm_agent_%s' % self._zhcp_node
|
|
self.topic = topics.AGENT
|
|
self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
|
|
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
|
|
|
|
self.context = context.get_admin_context_without_session()
|
|
|
|
self.endpoints = [self]
|
|
consumers = [[topics.PORT, topics.UPDATE],
|
|
[topics.NETWORK, topics.DELETE]]
|
|
self.connection = agent_rpc.create_consumers(self.endpoints,
|
|
self.topic,
|
|
consumers)
|
|
|
|
report_interval = cfg.CONF.AGENT.report_interval
|
|
if report_interval:
|
|
heartbeat = loopingcall.FixedIntervalLoopingCall(
|
|
self._report_state)
|
|
heartbeat.start(interval=report_interval)
|
|
|
|
def _report_state(self):
|
|
try:
|
|
self.state_rpc.report_state(self.context, self.agent_state)
|
|
self.agent_state.pop('start_flag', None)
|
|
except Exception:
|
|
LOG.exception(_("Failed reporting state!"))
|
|
|
|
def network_delete(self, context, network_id=None):
|
|
LOG.debug(_("Network delete received. UUID: %s"), network_id)
|
|
|
|
def port_update(self, context, **kwargs):
|
|
port = kwargs.get('port')
|
|
LOG.debug(_("Port update received. UUID: %s"), port)
|
|
|
|
if not port['id'] in self._port_map.keys():
|
|
# update a port which is not coupled to any NIC, nothing
|
|
# to do for a user based vswitch
|
|
return
|
|
|
|
vswitch = self._port_map[port['id']]['vswitch']
|
|
userid = self._port_map[port['id']]['userid']
|
|
if port['admin_state_up']:
|
|
self._utils.couple_nic_to_vswitch(vswitch, port['id'],
|
|
self._zhcp_node, userid)
|
|
self.plugin_rpc.update_device_up(self.context, port['id'],
|
|
self.agent_id)
|
|
else:
|
|
self._utils.uncouple_nic_from_vswitch(vswitch, port['id'],
|
|
self._zhcp_node, userid)
|
|
self.plugin_rpc.update_device_down(self.context, port['id'],
|
|
self.agent_id)
|
|
self._utils.put_user_direct_online(self._zhcp_node,
|
|
self._zhcp_userid)
|
|
|
|
def port_bound(self, port_id, net_uuid,
|
|
network_type, physical_network, segmentation_id, userid):
|
|
LOG.debug(_("Binding port %s"), port_id)
|
|
|
|
self._utils.grant_user(self._zhcp_node, physical_network, userid)
|
|
vdev = self._utils.couple_nic_to_vswitch(physical_network, port_id,
|
|
self._zhcp_node, userid)
|
|
self._utils.put_user_direct_online(self._zhcp_node,
|
|
self._zhcp_userid)
|
|
|
|
if network_type == p_const.TYPE_VLAN:
|
|
LOG.info(_('Binding VLAN, VLAN ID: %s'), segmentation_id)
|
|
self._utils.set_vswitch_port_vlan_id(segmentation_id, port_id,
|
|
vdev, self._zhcp_node,
|
|
physical_network)
|
|
else:
|
|
LOG.info(_('Bind %s mode done'), network_type)
|
|
|
|
def port_unbound(self, port_id):
|
|
LOG.debug(_("Unbinding port %s"), port_id)
|
|
# uncouple is not necessary, because revoke user will uncouple it
|
|
# automatically.
|
|
self._utils.revoke_user(self._zhcp_node,
|
|
self._port_map[port_id]['vswitch'],
|
|
self._port_map[port_id]['userid'])
|
|
|
|
def _update_ports(self, registered_ports):
|
|
ports_info = self._utils.get_nic_ids()
|
|
ports = set()
|
|
for p in ports_info:
|
|
target_host = p.split(',')[5].strip('"')
|
|
new_port_id = p.split(',')[2].strip('"')
|
|
if target_host == self._zhcp_node:
|
|
ports.add(new_port_id)
|
|
|
|
if ports == registered_ports:
|
|
return
|
|
|
|
added = ports - registered_ports
|
|
removed = registered_ports - ports
|
|
return {'current': ports, 'added': added, 'removed': removed}
|
|
|
|
def _treat_vif_port(self, port_id, network_id, network_type,
|
|
physical_network, segmentation_id,
|
|
admin_state_up):
|
|
node = self._utils.get_node_from_port(port_id)
|
|
userid = self._utils.get_userid_from_node(node)
|
|
LOG.info(_("Update port for node:%s") % node)
|
|
if admin_state_up:
|
|
self.port_bound(port_id, network_id, network_type,
|
|
physical_network, segmentation_id,
|
|
userid)
|
|
else:
|
|
self._utils.grant_user(self._zhcp_node, physical_network, userid)
|
|
return (node, userid)
|
|
|
|
def _treat_devices_added(self, devices):
|
|
for device in devices:
|
|
LOG.info(_("Adding port %s") % device)
|
|
try:
|
|
details = self.plugin_rpc.get_device_details(self.context,
|
|
device,
|
|
self.agent_id)
|
|
except Exception:
|
|
LOG.debug(_("Unable to get port details for %s:"), device)
|
|
continue
|
|
|
|
try:
|
|
if 'port_id' in details:
|
|
LOG.info(_("Port %(device)s updated."
|
|
" Details: %(details)s"),
|
|
{'device': device, 'details': details})
|
|
(node, userid) = self._treat_vif_port(
|
|
details['port_id'],
|
|
details['network_id'],
|
|
details['network_type'],
|
|
details['physical_network'],
|
|
details['segmentation_id'],
|
|
details['admin_state_up'])
|
|
# add device done, keep port map info
|
|
self._port_map[device] = {}
|
|
self._port_map[device]['userid'] = userid
|
|
self._port_map[device]['nodename'] = node
|
|
self._port_map[device]['vswitch'] = details[
|
|
'physical_network']
|
|
self._port_map[device]['vlan_id'] = details[
|
|
'segmentation_id']
|
|
|
|
# no rollback if this fails
|
|
self._utils.update_xcat_switch(details['port_id'],
|
|
details['physical_network'],
|
|
details['segmentation_id'])
|
|
if details.get('admin_state_up'):
|
|
LOG.debug(_("Setting status for %s to UP"), device)
|
|
self.plugin_rpc.update_device_up(
|
|
self.context, device, self.agent_id, cfg.CONF.host)
|
|
else:
|
|
LOG.debug(_("Setting status for %s to DOWN"), device)
|
|
self.plugin_rpc.update_device_down(
|
|
self.context, device, self.agent_id, cfg.CONF.host)
|
|
|
|
else:
|
|
LOG.debug(_("Device %s not defined on Neutron server"),
|
|
device)
|
|
continue
|
|
except Exception as e:
|
|
LOG.exception(_("Can not add device %(device)s: %(msg)s"),
|
|
{'device': device, 'msg': e})
|
|
continue
|
|
|
|
def _treat_devices_removed(self, devices):
|
|
for device in devices:
|
|
LOG.info(_("Removing port %s"), device)
|
|
try:
|
|
if not device in self._port_map:
|
|
LOG.warn(_("Can't find port %s in zvm agent"), device)
|
|
continue
|
|
|
|
self.port_unbound(device)
|
|
self.plugin_rpc.update_device_down(self.context,
|
|
device,
|
|
self.agent_id)
|
|
del self._port_map[device]
|
|
except Exception as e:
|
|
LOG.exception(_("Removing port failed %(device)s: %(msg)s"),
|
|
{'device': device, 'msg': e})
|
|
continue
|
|
|
|
def _process_network_ports(self, port_info):
|
|
if len(port_info['added']):
|
|
self._treat_devices_added(port_info['added'])
|
|
if len(port_info['removed']):
|
|
self._treat_devices_removed(port_info['removed'])
|
|
|
|
def xcatdb_daemon_loop(self):
|
|
ports = set()
|
|
# Get all exsited ports as configured
|
|
all_ports_info = self._update_ports(ports)
|
|
if all_ports_info is not None:
|
|
ports = all_ports_info['current']
|
|
|
|
connect = True
|
|
while True:
|
|
try:
|
|
start_time = time.time()
|
|
port_info = self._update_ports(ports)
|
|
|
|
# if no exception is raised in _update_ports,
|
|
# then the connection has recovered
|
|
if connect is False:
|
|
self._restart_handler.send(None)
|
|
connect = True
|
|
|
|
if port_info:
|
|
LOG.debug(_("Devices change!"))
|
|
self._process_network_ports(port_info)
|
|
ports = port_info['current']
|
|
except exception.zVMxCatRequestFailed as e:
|
|
LOG.error(_("Lost connection to xCAT. %s"), e)
|
|
connect = False
|
|
except Exception as e:
|
|
LOG.exception(_("error in xCAT DB query loop: %s"), e)
|
|
|
|
# sleep till end of polling interval
|
|
elapsed = (time.time() - start_time)
|
|
if (elapsed < self._polling_interval):
|
|
sleep_time = self._polling_interval - elapsed
|
|
LOG.debug(_("Sleep %s"), sleep_time)
|
|
time.sleep(sleep_time)
|
|
else:
|
|
LOG.debug(_("Looping iteration exceeded interval"))
|
|
|
|
def _init_xcat_mgt(self):
|
|
'''xCAT Management Node(MN) use the first flat network to manage all
|
|
the instances. So a flat network is required.
|
|
To talk to xCAT MN, xCAT MN requires every instance has a NIC which is
|
|
in the same subnet as xCAT. The xCAT MN's IP address is xcat_mgt_ip,
|
|
mask is xcat_mgt_mask in the config file,
|
|
by default neutron_zvm_plugin.ini.
|
|
'''
|
|
|
|
if not len(cfg.CONF.ml2_type_flat.flat_networks):
|
|
raise exception.zvmException(
|
|
msg=_('Can not find xCAT management network,'
|
|
'a flat network is required by xCAT.'))
|
|
self._utils.create_xcat_mgt_network(self._zhcp_node,
|
|
cfg.CONF.AGENT.xcat_mgt_ip,
|
|
cfg.CONF.AGENT.xcat_mgt_mask,
|
|
cfg.CONF.ml2_type_flat.flat_networks[0])
|
|
|
|
@restart_wrapper
|
|
def _handle_restart(self):
|
|
xcat_uptime, zvm_uptime = (None, None)
|
|
while True:
|
|
LOG.info(_("Try to reinitialize network ... "))
|
|
try:
|
|
tmp_new_time = self._utils.query_xcat_uptime(self._zhcp_node)
|
|
if xcat_uptime != tmp_new_time:
|
|
self._init_xcat_mgt()
|
|
xcat_uptime = tmp_new_time
|
|
|
|
tmp_new_time = self._utils.query_zvm_uptime(self._zhcp_node)
|
|
if zvm_uptime != tmp_new_time:
|
|
self._port_map = self._utils.re_grant_user(self._zhcp_node)
|
|
zvm_uptime = tmp_new_time
|
|
yield
|
|
except Exception:
|
|
LOG.error(_("Failed to handle restart,"
|
|
"try again in 5 seconds"))
|
|
time.sleep(5)
|
|
|
|
|
|
def main():
|
|
eventlet.monkey_patch()
|
|
cfg.CONF(project='neutron')
|
|
common_config.init(sys.argv[1:])
|
|
common_config.setup_logging()
|
|
|
|
agent = zvmNeutronAgent()
|
|
|
|
# Start to query xCAT DB
|
|
agent.xcatdb_daemon_loop()
|
|
LOG.info(_("z/VM agent initialized, now running... "))
|
|
sys.exit(0)
|