1
# Copyright 2013 OpenStack Foundation
4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5
# not use this file except in compliance with the License. You may obtain
6
# a copy of the License at
8
# http://www.apache.org/licenses/LICENSE-2.0
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
# License for the specific language governing permissions and limitations
17
Implements a Nexus-OS NETCONF over SSHv2 API Client
20
from oslo.utils import excutils
21
from oslo.utils import importutils
23
from neutron.openstack.common import log as logging
24
from neutron.plugins.ml2.drivers.cisco.nexus import config as conf
25
from neutron.plugins.ml2.drivers.cisco.nexus import constants as const
26
from neutron.plugins.ml2.drivers.cisco.nexus import exceptions as cexc
27
from neutron.plugins.ml2.drivers.cisco.nexus import nexus_snippets as snipp
29
LOG = logging.getLogger(__name__)
32
class CiscoNexusDriver(object):
33
"""Nexus Driver Main Class."""
36
self.nexus_switches = conf.ML2MechCiscoConfig.nexus_dict
39
def _import_ncclient(self):
40
"""Import the NETCONF client (ncclient) module.
42
The ncclient module is not installed as part of the normal Neutron
43
distributions. It is imported dynamically in this module so that
44
the import can be mocked, allowing unit testing without requiring
45
the installation of ncclient.
48
return importutils.import_module('ncclient.manager')
50
def _edit_config(self, nexus_host, target='running', config='',
51
allowed_exc_strs=None):
52
"""Modify switch config for a target config type.
54
:param nexus_host: IP address of switch to configure
55
:param target: Target config type
56
:param config: Configuration string in XML format
57
:param allowed_exc_strs: Exceptions which have any of these strings
58
as a subset of their exception message
59
(str(exception)) can be ignored
61
:returns None: if config was edited successfully
62
Exception object: if _edit_config() encountered an exception
63
containing one of allowed_exc_strs
65
:raises: NexusConfigFailed: if _edit_config() encountered an exception
66
not containing one of allowed_exc_strs
69
if not allowed_exc_strs:
71
mgr = self.nxos_connect(nexus_host)
73
LOG.debug("NexusDriver config: %s", config)
74
mgr.edit_config(target=target, config=config)
75
except Exception as e:
76
for exc_str in allowed_exc_strs:
79
# Raise a Neutron exception. Include a description of
80
# the original ncclient exception.
81
raise cexc.NexusConfigFailed(config=config, exc=e)
83
def nxos_connect(self, nexus_host):
84
"""Make SSH connection to the Nexus Switch."""
85
if getattr(self.connections.get(nexus_host), 'connected', None):
86
return self.connections[nexus_host]
89
self.ncclient = self._import_ncclient()
90
nexus_ssh_port = int(self.nexus_switches[nexus_host, 'ssh_port'])
91
nexus_user = self.nexus_switches[nexus_host, const.USERNAME]
92
nexus_password = self.nexus_switches[nexus_host, const.PASSWORD]
95
# With new ncclient version, we can pass device_params...
96
man = self.ncclient.connect(host=nexus_host,
99
password=nexus_password,
100
device_params={"name": "nexus"})
102
# ... but if that causes an error, we appear to have the old
103
# ncclient installed, which doesn't understand this parameter.
104
man = self.ncclient.connect(host=nexus_host,
107
password=nexus_password)
108
except Exception as e:
109
# Raise a Neutron exception. Include a description of
110
# the original ncclient exception.
111
raise cexc.NexusConnectFailed(nexus_host=nexus_host, exc=e)
113
self.connections[nexus_host] = man
114
return self.connections[nexus_host]
116
def create_xml_snippet(self, customized_config):
117
"""Create XML snippet.
119
Creates the Proper XML structure for the Nexus Switch Configuration.
121
conf_xml_snippet = snipp.EXEC_CONF_SNIPPET % (customized_config)
122
return conf_xml_snippet
124
def create_vlan(self, nexus_host, vlanid, vlanname):
125
"""Create a VLAN on Nexus Switch given the VLAN ID and Name."""
126
confstr = self.create_xml_snippet(
127
snipp.CMD_VLAN_CONF_SNIPPET % (vlanid, vlanname))
128
self._edit_config(nexus_host, target='running', config=confstr)
130
# Enable VLAN active and no-shutdown states. Some versions of
131
# Nexus switch do not allow state changes for the extended VLAN
132
# range (1006-4094), but these errors can be ignored (default
133
# values are appropriate).
134
for snippet in [snipp.CMD_VLAN_ACTIVE_SNIPPET,
135
snipp.CMD_VLAN_NO_SHUTDOWN_SNIPPET]:
137
confstr = self.create_xml_snippet(snippet % vlanid)
142
allowed_exc_strs=["Can't modify state for extended",
143
"Command is only allowed on VLAN"])
144
except cexc.NexusConfigFailed:
145
with excutils.save_and_reraise_exception():
146
self.delete_vlan(nexus_host, vlanid)
148
def delete_vlan(self, nexus_host, vlanid):
149
"""Delete a VLAN on Nexus Switch given the VLAN ID."""
150
confstr = snipp.CMD_NO_VLAN_CONF_SNIPPET % vlanid
151
confstr = self.create_xml_snippet(confstr)
152
self._edit_config(nexus_host, target='running', config=confstr)
154
def build_intf_confstr(self, snippet, intf_type, interface, vlanid):
155
"""Build the VLAN config string xml snippet to be used."""
156
confstr = snippet % (intf_type, interface, vlanid, intf_type)
157
confstr = self.create_xml_snippet(confstr)
158
LOG.debug("NexusDriver: %s", confstr)
161
def enable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type,
163
"""Enable a VLAN on a trunk interface."""
164
# Configure a new VLAN into the interface using 'ADD' keyword
165
confstr = self.build_intf_confstr(
166
snippet=snipp.CMD_INT_VLAN_ADD_SNIPPET,
171
exc_str = ["switchport trunk allowed vlan list is empty"]
172
ret_exc = self._edit_config(nexus_host, target='running',
174
allowed_exc_strs=exc_str)
176
# If no switchports have been configured on the switch
177
# before the new 'ADD', configure the VLAN into the
178
# interface without the keyword so as to create a vlan list
179
confstr = self.build_intf_confstr(
180
snippet=snipp.CMD_INT_VLAN_SNIPPET,
185
self._edit_config(nexus_host, target='running',
188
def disable_vlan_on_trunk_int(self, nexus_host, vlanid, intf_type,
190
"""Disable a VLAN on a trunk interface."""
191
confstr = (snipp.CMD_NO_VLAN_INT_SNIPPET %
192
(intf_type, interface, vlanid, intf_type))
193
confstr = self.create_xml_snippet(confstr)
194
self._edit_config(nexus_host, target='running', config=confstr)
196
def create_and_trunk_vlan(self, nexus_host, vlan_id, vlan_name,
197
intf_type, nexus_port):
198
"""Create VLAN and trunk it on the specified ports."""
199
self.create_vlan(nexus_host, vlan_id, vlan_name)
200
LOG.debug("NexusDriver created VLAN: %s", vlan_id)
202
self.enable_vlan_on_trunk_int(nexus_host, vlan_id, intf_type,