Exemplo n.º 1
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))
Exemplo n.º 2
0
    def _JunosLoad(self,
                   operation,
                   filename,
                   canary=False,
                   skip_show_compare=False,
                   skip_commit_check=False,
                   rollback_patch=None):
        """Loads the configuration to the remote device using a given operation.

    Args:
      operation: A string, the load operation (e.g., 'replace', 'override').
      filename: A string, the remote temporary filename to stage configuration.
      canary: A boolean, if True, only canary check the configuration, don't
        apply it.
      skip_show_compare: A boolean, if True, "show | compare" will be skipped.
        This is a temporary flag due to a JunOS bug and may be removed in the
        future.
      skip_commit_check: A boolean, if True, "commit check" (running the commit
        scripts) will be skipped in canary mode.
      rollback_patch: None or a string, optional filename into which to
        record and return a patch to rollback the config change.

    Returns:
      A base_device.SetConfigResult, all responses from the router during the
      check/load operation, plus any optional extras.
    """

        show_compare = 'show | compare; '
        if skip_show_compare:
            show_compare = ''
        if canary:
            commit_check = 'commit check; '
            if skip_commit_check:
                commit_check = ''
            cmd = ('edit exclusive; load %s %s; %s%srollback 0; exit' %
                   (operation, filename, show_compare, commit_check))
        else:
            save_rollback_patch = ''
            if rollback_patch:
                save_rollback_patch = (
                    'rollback 1; show | compare | save %s; rollback;' %
                    rollback_patch)
            cmd = ('edit exclusive; load %s %s; %s'
                   'commit comment "push: load %s %s";%s exit' %
                   (operation, filename, show_compare, operation, filename,
                    save_rollback_patch))
        result = base_device.SetConfigResult()
        result.transcript = self._Cmd(cmd)
        self._RaiseExceptionIfLoadError(result.transcript,
                                        expect_config_check=canary
                                        and not skip_commit_check,
                                        expect_commit=not canary)
        return result
Exemplo n.º 3
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
Exemplo n.º 4
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
Exemplo n.º 5
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