Ejemplo n.º 1
0
    def _RaiseExceptionIfLoadError(result,
                                   expect_config_check=False,
                                   expect_commit=False):
        """Checks if a result string from a load configuration contains an error.

    Args:
      result: A string, the result of loading the configuration.
      expect_config_check: A boolean. If true, then the exception-raising code
        will raise a special "configuration check failed" exception if the
        string "configuration check succeeds" isn't found.
      expect_commit: A boolean. If True, then the function raises an exception
        if the string "commit complete" isn't found on a line by itself.

    Raises:
      An exception derived from exceptions.SetConfigError if the result
      indicates an error, else nothing.
    """
        # Remove output assumed to be part of diffs or quoted parts of the input.
        # Lines that are considered to be part of diffs start with + or - or !,
        # start and end with square brackets like "[edit ... ]", or immediately
        # follow a line that starts and ends with square brackets.
        lines = []
        last_line_started_diff = False
        for line in result.splitlines():
            if last_line_started_diff:
                last_line_started_diff = False
                # Ignore the line.
            elif line.startswith('[') and line.endswith(']'):
                last_line_started_diff = True
            else:
                lines.append(JunosDevice._CleanupErrorLine(line))

        for error in JunosDevice.JUNOS_LOAD_ERRORS:
            if any(error in line for line in lines):
                break
        else:
            # No special "error" string found, check for "commit complete".
            if expect_commit and all('commit complete' not in line
                                     for line in lines):
                raise exceptions.SetConfigError(
                    '"commit complete" expected, but not found in output:\n%s'
                    % result)
            return

        # Raise the right type of exception based on the error string found.
        if any('syntax error' in line for line in lines):
            raise exceptions.SetConfigSyntaxError(
                'Device reports a syntax error in the configuration.\n%s' %
                result)
        elif expect_config_check and all(
                'configuration check succeeds' not in line for line in lines):
            raise exceptions.SetConfigSyntaxError(
                'Configuration check failed.\n%s' % result)
        else:
            raise exceptions.SetConfigError(
                'Error occurred during config load.\n%s' % result)
Ejemplo n.º 2
0
        def SendAndWait(command):
            """Sends a command and waits for a response.

      Args:
        command: str; A single config line.

      Returns:
        A string; the last response.

      Raises:
        exceptions.SetConfigError: When we unexpectedly exit configuration mode
          while setting config.
      """
            self._connection.child.send(command + '\r')
            self._connection.child.expect('\r\n',
                                          timeout=self.timeout_response)
            pindex = self._connection.child.expect(
                [self._connection.config_prompt, self._connection.re_prompt],
                timeout=self.timeout_response,
                searchwindowsize=128)
            # We unexpectedly exited config mode. Too many exits or ctrl-z.
            if pindex == 1:
                raise exceptions.SetConfigError(
                    'Unexpectedly exited config mode after line: %s' % command)
            return self._connection.child.before.replace('\r\n', os.linesep)
Ejemplo n.º 3
0
  def _SetConfig(self, destination_file, data, canary):
    # Canarying is not supported on BROCADE.
    if canary:
      raise exceptions.SetConfigCanaryingError('%s devices do not support '
                                               'configuration canarying.' %
                                               self.vendor_name)
    # The result object.
    result = base_device.SetConfigResult()
    # Check for a connection to the Brocade.
    if not self._GetConnected():
      raise exceptions.SetConfigError('Cannot use unless already '
                                      'connected to the device.')

    if destination_file in self.NON_FILE_DESTINATIONS:
      # Use a random remote file name
      file_name = 'push.%s' % os.urandom(8).encode('hex')
    else:
      # Okay, the user is just copying a file, not a configuraiton into either
      # startup-config or running-config, therefore we should use the entire
      # path.
      file_name = destination_file

    # Copy the file to the router using SCP.
    scp = pexpect_connection.ScpPutConnection(
        host=self.loopback_ipv4,
        username=self._username,
        password=self._password)

    # This is a workaround. Brocade case: 537017.
    # Brocade changed all the filename to lowercases after scp
    file_name = file_name.lower()
    try:
      scp.Copy(data, destination_file='slot1:' + file_name)
    except pexpect_connection.Error, e:
      raise exceptions.SetConfigError(
          'Failed to copy configuration to remote device. %s' % str(e))
Ejemplo n.º 4
0
    def SetConfig(self,
                  destination_file,
                  data,
                  canary,
                  juniper_skip_show_compare=False,
                  juniper_skip_commit_check=False,
                  juniper_get_rollback_patch=False):
        """Updates a devices' configuration.

    Concrete classes must define _SetConfig with the same arguments.

    Args:
      destination_file: A string.  A path to a file on the device.
      data: A string, the configuration data to set.
      canary: A boolean, whether to canary, rather than set, the configuration.
      juniper_skip_show_compare: A boolean, temporary flag to skip
          'show | compare' on Junipers due to a bug.
      juniper_skip_commit_check: A boolean, flag to skip 'commit check' on
          Junipers when doing a canary.
      juniper_get_rollback_patch: A boolean, optionally try to retrieve a
          patch to rollback the config change.

    Returns:
      A SetConfigResult.  Transcript of any device interaction that occurred
      during the operation, plus any optional extras.

    Raises:
      exceptions.SetConfigError: the SetConfig operation failed.
      exceptions.SetConfigSyntaxError: the configuration data had a syntax
          error.
    """
        if destination_file in self.unsupported_non_file_destinations:
            raise exceptions.SetConfigError(
                '%s devices do not support %s as a destination.' %
                (self.vendor_name, destination_file))
        if ((juniper_skip_show_compare or juniper_skip_commit_check
             or juniper_get_rollback_patch)
                and self.__class__.__name__ == 'JunosDevice'):
            return self._SetConfig(
                destination_file,
                data,
                canary,
                skip_show_compare=juniper_skip_show_compare,
                skip_commit_check=juniper_skip_commit_check,
                get_rollback_patch=juniper_get_rollback_patch)
        else:
            return self._SetConfig(destination_file, data, canary)
Ejemplo n.º 5
0
class BrocadeMlxDevice(BrocadeDevice):
    """A base device model suitable for Brocade MLX devices.

  See the base_device.BaseDevice method docstrings.
  """

    disable_pager_command = _BROCADE_MLX_DISABLE_PAGER

    def __init__(self, **kwargs):
        self.vendor_name = 'brocademlx'
        super(BrocadeMlxDevice, self).__init__(**kwargs)

    def _GetFileSize(self, file_name, data):
        """Gets the size of a file in Brocade 'dir' output.

    Args:
      file_name: A string, the file name.
      data: A string, the Brocade's "dir" output.

    Returns:
      An int, the file size, or None if the value could not be determined.
    """
        for line in data.splitlines():
            match = RE_FILE_LISTING.match(line)
            if match is not None:
                (file_size, fname) = match.groups()
                for char in string.punctuation:
                    file_size = file_size.replace(char, '')
                if file_name.strip() == fname.strip():
                    try:
                        return int(file_size)
                    except ValueError:
                        continue
        return None

    def _SetConfig(self, destination_file, data, canary):
        # Canarying is not supported on BROCADE.
        if canary:
            raise exceptions.SetConfigCanaryingError(
                '%s devices do not support '
                'configuration canarying.' % self.vendor_name)
        # The result object.
        result = base_device.SetConfigResult()
        # Check for a connection to the Brocade.
        if not self._GetConnected():
            raise exceptions.SetConfigError('Cannot use unless already '
                                            'connected to the device.')

        if destination_file in self.NON_FILE_DESTINATIONS:
            # Use a random remote file name
            file_name = 'push.%s' % os.urandom(8).encode('hex')
        else:
            # Okay, the user is just copying a file, not a configuraiton into either
            # startup-config or running-config, therefore we should use the entire
            # path.
            file_name = destination_file

        # Copy the file to the router using SCP.
        scp = pexpect_connection.ScpPutConnection(host=self.loopback_ipv4,
                                                  username=self._username,
                                                  password=self._password)

        # This is a workaround. Brocade case: 537017.
        # Brocade changed all the filename to lowercases after scp
        file_name = file_name.lower()
        try:
            scp.Copy(data, destination_file='slot1:' + file_name)
        except pexpect_connection.Error, e:
            raise exceptions.SetConfigError(
                'Failed to copy configuration to remote device. %s' % str(e))
        # Now that everything is OK locally and the file has been copied,
        # check the file and tell the device to set the new configuration.
        try:
            # Get the file size on the Brocade.
            try:
                cmd = 'dir /slot1/%s' % file_name
                dir_output = self._Cmd(cmd)
            except exceptions.CmdError, e:
                if 'Invalid input at' in str(e):
                    raise exceptions.AuthenticationError(
                        'Username/password for %s(%s) has insufficient privileges '
                        'to set configuration.' %
                        (self.host, self.loopback_ipv4))
                else:
                    raise exceptions.SetConfigError(
                        'Could not traverse directory '
                        'output.  Command was: %r. '
                        'Error: %r' % (cmd, str(e)))
            destination_file_size = self._GetFileSize(file_name, dir_output)
            # We couldn't parse the output for some reason.
            if destination_file_size is None:
                raise exceptions.SetConfigError(
                    'Could not find or parse remote '
                    'file size after copy to device.')

            # Verify file is the correct size on the Brocade.
            # This should use a checksum (e.g. MD5 or SHA1); Brocade case: 609719.
            if destination_file_size != len(data):
                raise exceptions.SetConfigError(
                    'File transfer corrupted. Source file was: %d bytes, '
                    'Destination file was: %d bytes.' %
                    (len(data), destination_file_size))

            # Copy the file from flash to the
            # destination(running-config, startup-config)
            if destination_file == self.CONFIG_STARTUP:
                try:
                    self._connection.child.send(
                        'copy slot1 startup-config %s\r' % file_name)
                    time.sleep(MINOR_PAUSE)
                    pindex = self._connection.child.expect(
                        ['Total bytes', self._connection.re_prompt, 'Error'],
                        timeout=self.timeout_act_user)
                    if pindex == 2:
                        raise exceptions.SetConfigError(
                            'Could not copy temporary '
                            'file to startup-config.')
                except (pexpect.EOF, pexpect.TIMEOUT), e:
                    raise exceptions.SetConfigError(str(e))
Ejemplo n.º 6
0
    def _SetConfig(self, unused_destination_file, data, canary):
        """Upload config to a Brocade router (TI/FI).

    Args:
      unused_destination_file: Unused.
      data: A string, the data to copy to destination_file.
      canary: A boolean, if True, only canary check the configuration, don't
        apply it.

    Returns:
      A base_device.SetConfigResult.
      Transcript of any device interaction that occurred during the _SetConfig.

    Raises:
      exceptions.CmdError: An error occurred inside the call to _Cmd.
    """
        # Canarying is not supported on BROCADE.
        if canary:
            raise exceptions.SetConfigCanaryingError(
                '%s devices do not support '
                'configuration canarying.' % self.vendor_name)
        # The result object.
        result = base_device.SetConfigResult()
        # Check for a connection to the Brocade.
        if not self._GetConnected():
            raise exceptions.SetConfigError('Cannot use unless already '
                                            'connected to the device.')

        # Derive our config prompt from the discovered prompt.
        self._connection.config_prompt = re.compile(
            self._connection.re_prompt.pattern[:-2] + r'\(config\S*\)' +
            self._connection.re_prompt.pattern[-2:])

        # Enter config mode.
        self._connection.child.send('configure terminal\r')
        self._connection.child.expect('\r\n', timeout=self.timeout_response)
        self._connection.child.expect(self._connection.config_prompt,
                                      timeout=self.timeout_response,
                                      searchwindowsize=128)

        def SendAndWait(command):
            """Sends a command and waits for a response.

      Args:
        command: str; A single config line.

      Returns:
        A string; the last response.

      Raises:
        exceptions.SetConfigError: When we unexpectedly exit configuration mode
          while setting config.
      """
            self._connection.child.send(command + '\r')
            self._connection.child.expect('\r\n',
                                          timeout=self.timeout_response)
            pindex = self._connection.child.expect(
                [self._connection.config_prompt, self._connection.re_prompt],
                timeout=self.timeout_response,
                searchwindowsize=128)
            # We unexpectedly exited config mode. Too many exits or ctrl-z.
            if pindex == 1:
                raise exceptions.SetConfigError(
                    'Unexpectedly exited config mode after line: %s' % command)
            return self._connection.child.before.replace('\r\n', os.linesep)

        lines = [x.strip() for x in data.splitlines()]
        # Remove any 'end' lines. Multiple ends could be bad.
        lines = [line for line in lines if line != 'end']

        for line in lines:
            if next((line for prefix in self.verboten_config
                     if line.startswith(prefix)), False):
                raise exceptions.CmdError(
                    'Command %s is not permitted on Brocade devices.' % line)
            if line:
                line_result = SendAndWait(line)
                if (line_result.startswith('Invalid input -> ') or line_result
                        == 'Not authorized to execute this command.\n'):
                    raise exceptions.CmdError('Command failed: %s' %
                                              line_result)

        self._connection.child.send('end\r')
        self._connection.child.expect(self._connection.re_prompt,
                                      timeout=self.timeout_act_user)
        self._connection.child.send('wr mem\r')
        self._connection.child.expect(self._connection.re_prompt,
                                      timeout=self.timeout_act_user)
        self._Disconnect()
        result.transcript = 'SetConfig applied the file successfully.'
        return result
Ejemplo n.º 7
0
                    pindex = self._connection.child.expect(
                        ['Total bytes', self._connection.re_prompt, 'Error'],
                        timeout=self.timeout_act_user)
                    if pindex == 2:
                        raise exceptions.SetConfigError(
                            'Could not copy temporary '
                            'file to startup-config.')
                except (pexpect.EOF, pexpect.TIMEOUT), e:
                    raise exceptions.SetConfigError(str(e))
            elif destination_file == self.CONFIG_RUNNING:
                try:
                    # This is not working, unfortunately. Cannot copy a file to a running
                    # config, raised support case RFE2901
                    self._Cmd('copy slot1 running-config %s' % file_name)
                except exceptions.CmdError, e:
                    raise exceptions.SetConfigError(str(e))
                # We need to 'write memory' if we are doing running-config.
                logging.vlog(
                    3, 'Attempting to copy running-config to startup-config '
                    'on %s(%s)', self.host, self.loopback_ipv4)
                try:
                    self._Cmd('wr mem')
                except exceptions.CmdError, e:
                    raise exceptions.SetConfigError(
                        'Failed to write startup-config '
                        'for %s(%s). Error was: %s' %
                        (self.host, self.loopback_ipv4, str(e)))

        finally:
            # Now remove the remote temporary file.
            # If this fails, we may have already copied the file, so log warnings
Ejemplo n.º 8
0
  def _SetConfig(self, destination_file, data, canary):
    # Canarying is not supported on ASA.
    if canary:
      raise exceptions.SetConfigCanaryingError('%s devices do not support '
                                               'configuration canarying.' %
                                               self.vendor_name)
    # We only support copying to 'running-config' or 'startup-config' on ASA.
    if destination_file not in ('running-config', 'startup-config'):
      raise exceptions.SetConfigError('destination_file argument must be '
                                      '"running-config" or "startup-config" '
                                      'for %s devices.' % self.vendor_name)
    # Result object.
    result = base_device.SetConfigResult()

    # Get the MD5 sum of the file.
    local_digest = hashlib.md5(data).hexdigest()

    try:
      # Get the working path from the remote device
      remote_path = 'nvram:/'
    except exceptions.CmdError as e:
      msg = 'Error obtaining working directory: %s' % e
      logging.error(msg)
      raise exceptions.SetConfigError(msg)

    # Use a random remote file name
    remote_tmpfile = '%s/push.%s' % (
        remote_path.rstrip(), os.urandom(8).encode('hex'))

    # Upload the file to the device.
    scp = pexpect_connection.ScpPutConnection(
        self.loopback_ipv4,
        username=self._username,
        password=self._password)
    try:
      scp.Copy(data, remote_tmpfile)
    except pexpect_connection.Error as e:
      raise exceptions.SetConfigError(
          'Failed to copy configuration to remote device. %s' % str(e))

    # Get the file size on the router.
    try:
      # Get the MD5 hexdigest of the file on the remote device.
      try:
        verify_output = self._Cmd('verify /md5 %s' % remote_tmpfile)
        match = MD5_RE.search(verify_output)
        if match is not None:
          remote_digest = match.group(1)
        else:
          raise exceptions.SetConfigError(
              'The "verify /md5 <filename>" command did not produce '
              'expected results. It returned: %r' % verify_output)
      except exceptions.CmdError as e:
        raise exceptions.SetConfigError(
            'The MD5 hash command on the router did not succed. '
            'The device may not support: "verify /md5 <filename>"')
      # Verify the local_digest and remote_digest are the same.
      if local_digest != remote_digest:
        raise exceptions.SetConfigError(
            'File transfer to remote host corrupted. Local digest: %r, '
            'Remote digest: %r' % (local_digest, remote_digest))

      # Copy the file from flash to the
      # destination(running-config, startup-config).
      # Catch errors that may occur during application, and report
      # these to the user.
      try:
        self._connection.child.send(
            'copy %s %s\r' % (remote_tmpfile, destination_file))
        pindex = self._connection.child.expect(
            [r'Destination filename \[%s\]\?' % destination_file,
             r'%\s*\S*.*',
             r'%Error.*',
             self._connection.re_prompt],
            timeout=self.timeout_act_user)
        if pindex == 0:
          self._connection.child.send('\r')
          try:
            pindex = self._connection.child.expect(
                [r'Invalid input detected',
                 self._connection.re_prompt,
                 r'%Warning:There is a file already existing.*'
                 'Do you want to over write\? \[confirm\]'],
                timeout=self.timeout_act_user)
            if pindex == 0:
              # Search again using findall to get all bad lines.
              bad_lines = re.findall(
                  r'^(.*)$[\s\^]+% Invalid input',
                  self._connection.child.match.string,
                  re.MULTILINE)
              raise exceptions.SetConfigSyntaxError(
                  'Configuration loaded, but with bad lines:\n%s' %
                  '\n'.join(bad_lines))
            if pindex == 2:
              # Don't over-write.
              self._connection.child.send('n')
              raise exceptions.SetConfigError(
                  'Destination file %r already exists, cannot overwrite.'
                  % destination_file)
          except (pexpect.EOF, pexpect.TIMEOUT) as e:
            raise exceptions.SetConfigError(
                'Copied file to device, but did not '
                'receive prompt afterwards. %s %s' %
                (self._connection.child.before, self._connection.child.after))

        elif pindex == 2:
          print "MATCHED 2"
          # The expect does a re.search, search again using findall to get all
          raise exceptions.SetConfigError('Could not copy temporary '
                                          'file to %s.' % destination_file)
      except (pexpect.EOF, pexpect.TIMEOUT) as e:
        raise exceptions.SetConfigError(
            'Attempted to copy to bootflash, but a timeout occurred.')

      # We need to 'write memory' if we are doing running-config.
      if destination_file == 'running-config':
        logging.debug('Attempting to copy running-config to startup-config '
                     'on %s(%s)', self.host, self.loopback_ipv4)
        try:
          self._Cmd('wr mem')
        except exceptions.CmdError as e:
          raise exceptions.SetConfigError('Failed to write startup-config '
                                          'for %s(%s). Changes applied. '
                                          'Error was: %s' %
                                          (self.host, self.loopback_ipv4,
                                           str(e)))
    finally:
      try:
        self._DeleteFile(remote_tmpfile)
      except DeleteFileError as e:
        result.transcript = 'SetConfig warning: %s' % str(e)
        logging.warn(result.transcript)

    # And finally, return the result text.
    return result
Ejemplo n.º 9
0
    def _SetConfig(self,
                   destination_file,
                   data,
                   canary,
                   skip_show_compare=False,
                   skip_commit_check=False,
                   get_rollback_patch=False):
        copied = False

        file_ptr = tempfile.NamedTemporaryFile()
        rollback_patch_ptr = tempfile.NamedTemporaryFile()
        rollback_patch = None
        # Setting the file name based upon if we are trying to copy a file or
        # we are trying to copy a config into the control plane.
        if destination_file in self.NON_FILE_DESTINATIONS:
            file_name = os.path.basename(file_ptr.name)
            if get_rollback_patch:
                rollback_patch = os.path.basename(rollback_patch_ptr.name)
        else:
            file_name = destination_file
            logging.info('Remote file path: %s', file_name)

        try:
            file_ptr.write(data)
            file_ptr.flush()
        except IOError:
            raise exceptions.SetConfigError(
                'Could not open temporary file %r' % file_ptr.name)
        result = base_device.SetConfigResult()
        try:
            # Copy the file to the remote device.
            try:
                self._SendFileViaSftp(local_filename=file_ptr.name,
                                      remote_filename=file_name)
                copied = True
            except (paramiko.SFTPError, IOError) as e:
                # _SendFileViaSftp puts the normalized destination path in e.args[1].
                msg = 'SFTP failed (filename %r to device %s(%s):%s): %s: %s' % (
                    file_ptr.name, self.host, self.loopback_ipv4, e.args[1],
                    e.__class__.__name__, e.args[0])
                raise exceptions.SetConfigError(msg)

            if not self._ChecksumsMatch(local_file_name=file_ptr.name,
                                        remote_file_name=file_name):
                raise exceptions.SetConfigError(
                    'Local and remote file checksum mismatch.')

            if self.CONFIG_RUNNING == destination_file:
                operation = 'replace'
            elif self.CONFIG_STARTUP == destination_file:
                operation = 'override'
            elif self.CONFIG_PATCH == destination_file:
                operation = 'patch'
            else:
                result.transcript = 'SetConfig uploaded the file successfully.'
                return result
            if canary:
                logging.debug('Canary syntax checking configuration file %r.',
                              file_name)
                result = self._JunosLoad(operation,
                                         file_name,
                                         canary=True,
                                         skip_show_compare=skip_show_compare,
                                         skip_commit_check=skip_commit_check)
            else:
                logging.debug(
                    'Setting destination %r with configuration file %r.',
                    destination_file, file_name)
                result = self._JunosLoad(operation,
                                         file_name,
                                         skip_show_compare=skip_show_compare,
                                         skip_commit_check=skip_commit_check,
                                         rollback_patch=rollback_patch)

                if rollback_patch:
                    try:
                        self._GetFileViaSftp(
                            local_filename=rollback_patch_ptr.name,
                            remote_filename=rollback_patch)
                        result.rollback_patch = rollback_patch_ptr.read()
                    except (paramiko.SFTPError, IOError) as e:
                        # _GetFileViaSftp puts the normalized source path in e.args[1].
                        result.transcript += (
                            'SFTP rollback patch retrieval failed '
                            '(filename %r from device %s(%s):%s): %s: %s' %
                            (rollback_patch_ptr.name, self.host,
                             self.loopback_ipv4, e.args[1],
                             e.__class__.__name__, e.args[0]))

            # Return the diagnostic results as the (optional) result.
            return result

        finally:
            local_delete_exception = None
            # Unlink the original temporary file.
            try:
                logging.info('Deleting the file on the local machine: %s',
                             file_ptr.name)
                file_ptr.close()
            except IOError:
                local_delete_exception = exceptions.SetConfigError(
                    'Could not close temporary file.')

            local_rollback_patch_delete_exception = None
            # Unlink the rollback patch temporary file.
            try:
                logging.info('Deleting the file on the local machine: %s',
                             rollback_patch_ptr.name)
                rollback_patch_ptr.close()
            except IOError:
                local_rollback_patch_delete_exception = exceptions.SetConfigError(
                    'Could not close temporary rollback patch file.')

            # If we copied the file to the router and we were pushing a configuration,
            # delete the temporary file off the router.
            if copied and destination_file in self.NON_FILE_DESTINATIONS:
                logging.info('Deleting file on the router: %s', file_name)
                self.Cmd('file delete ' + file_name)

            # Delete any rollback patch file too.
            if rollback_patch:
                logging.info('Deleting patch on the router: %s',
                             rollback_patch)
                self.Cmd('file delete ' + rollback_patch)

            # If we got an exception on the local file delete, but did not get a
            # (more important) exception on the remote delete, raise the local delete
            # exception.
            #
            # pylint is confused by the re-raising
            # pylint: disable=raising-bad-type
            if local_delete_exception is not None:
                raise local_delete_exception
            if local_rollback_patch_delete_exception is not None:
                raise local_rollback_patch_delete_exception