Multi-vendor Modular Layer 2 (ML2) driver.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juniper.py 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. # Copyright (c) 2018 StackHPC Ltd.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. from oslo_log import log as logging
  15. import tenacity
  16. from networking_generic_switch.devices import netmiko_devices
  17. from networking_generic_switch.devices import utils as device_utils
  18. from networking_generic_switch import exceptions as exc
  19. LOG = logging.getLogger(__name__)
  20. # Internal ngs options will not be passed to driver.
  21. JUNIPER_INTERNAL_OPTS = [
  22. # Timeout (seconds) for committing configuration changes.
  23. {'name': 'ngs_commit_timeout', 'default': 60},
  24. # Interval (seconds) between attempts to commit configuration changes.
  25. {'name': 'ngs_commit_interval', 'default': 5},
  26. ]
  27. class Juniper(netmiko_devices.NetmikoSwitch):
  28. ADD_NETWORK = (
  29. 'set vlans {network_id} vlan-id {segmentation_id}',
  30. )
  31. DELETE_NETWORK = (
  32. 'delete vlans {network_id}',
  33. )
  34. PLUG_PORT_TO_NETWORK = (
  35. # Delete any existing VLAN associations - only one VLAN may be
  36. # associated with an access mode port.
  37. 'delete interface {port} unit 0 family ethernet-switching '
  38. 'vlan members',
  39. 'set interface {port} unit 0 family ethernet-switching '
  40. 'vlan members {segmentation_id}',
  41. )
  42. DELETE_PORT = (
  43. 'delete interface {port} unit 0 family ethernet-switching '
  44. 'vlan members',
  45. )
  46. ADD_NETWORK_TO_TRUNK = (
  47. 'set interface {port} unit 0 family ethernet-switching '
  48. 'vlan members {segmentation_id}',
  49. )
  50. REMOVE_NETWORK_FROM_TRUNK = (
  51. 'delete interface {port} unit 0 family ethernet-switching '
  52. 'vlan members {segmentation_id}',
  53. )
  54. def __init__(self, device_cfg):
  55. super(Juniper, self).__init__(device_cfg)
  56. # Do not expose Juniper internal options to device config.
  57. for opt in JUNIPER_INTERNAL_OPTS:
  58. opt_name = opt['name']
  59. if opt_name in self.config:
  60. self.ngs_config[opt_name] = self.config.pop(opt_name)
  61. elif 'default' in opt:
  62. self.ngs_config[opt_name] = opt['default']
  63. def send_config_set(self, net_connect, cmd_set):
  64. """Send a set of configuration lines to the device.
  65. :param net_connect: a netmiko connection object.
  66. :param cmd_set: a list of configuration lines to send.
  67. :returns: The output of the configuration commands.
  68. """
  69. # We use the private configuration mode, which hides the configuration
  70. # changes of concurrent sessions from us, and discards uncommitted
  71. # changes on termination of the session. See
  72. # https://www.juniper.net/documentation/en_US/junos/topics/concept/junos-cli-multiple-users-usage-overview.html.
  73. net_connect.config_mode(config_command='configure private')
  74. # Don't exit configuration mode, as we still need to commit the changes
  75. # in save_configuration().
  76. return net_connect.send_config_set(config_commands=cmd_set,
  77. exit_config_mode=False)
  78. def save_configuration(self, net_connect):
  79. """Save the device's configuration.
  80. :param net_connect: a netmiko connection object.
  81. :raises GenericSwitchNetmikoConfigError if saving the configuration
  82. fails.
  83. """
  84. # Junos configuration is transactional, and requires an explicit commit
  85. # of changes in order for them to be applied. Since committing requires
  86. # an exclusive lock on the configuration database, it can fail if
  87. # another session has a lock. We use a retry mechanism to work around
  88. # this.
  89. class DBLocked(Exception):
  90. """Switch configuration DB is locked by another user."""
  91. def __init__(self, err):
  92. self.err = err
  93. @tenacity.retry(
  94. # Log a message after each failed attempt.
  95. after=tenacity.after_log(LOG, logging.DEBUG),
  96. # Reraise exceptions if our final attempt fails.
  97. reraise=True,
  98. # Retry on failure to commit the configuration due to the DB
  99. # being locked by another session.
  100. retry=(tenacity.retry_if_exception_type(DBLocked)),
  101. # Stop after the configured timeout.
  102. stop=tenacity.stop_after_delay(
  103. int(self.ngs_config['ngs_commit_timeout'])),
  104. # Wait for the configured interval between attempts.
  105. wait=tenacity.wait_fixed(
  106. int(self.ngs_config['ngs_commit_interval'])),
  107. )
  108. def commit():
  109. try:
  110. net_connect.commit()
  111. except ValueError as e:
  112. # Netmiko raises ValueError on commit failure, and appends the
  113. # CLI output to the exception message. Raise a more specific
  114. # exception for a locked DB, on which tenacity will retry.
  115. if "error: configuration database locked" in str(e):
  116. raise DBLocked(e)
  117. raise
  118. try:
  119. commit()
  120. except DBLocked as e:
  121. msg = ("Reached timeout waiting for switch configuration DB lock: "
  122. "%s" % e.err)
  123. LOG.error(msg)
  124. raise exc.GenericSwitchNetmikoConfigError(
  125. config=device_utils.sanitise_config(self.config), error=msg)
  126. except ValueError as e:
  127. msg = "Failed to commit configuration: %s" % e
  128. LOG.error(msg)
  129. raise exc.GenericSwitchNetmikoConfigError(
  130. config=device_utils.sanitise_config(self.config), error=msg)