def generate_changeset(old, new, comment=None):
  """Diff two XML configs and return an object with changes to be written.

  Args: old, new: lxml.etree.Element (<ResourceRecordSets>).
  Returns: lxml.etree.ETree (<ChangeResourceRecordSetsRequest>) or None"""
  rrsets_tag = '{%s}ResourceRecordSets' % R53_XMLNS
  if rrsets_tag not in (old.tag, new.tag):
    log.error('both configs must be ResourceRecordSets tags. old: %s, new: %s' % (old.tag, new.tag))
    raise InvalidArgumentException()
  if comment is None:
    comment = 'Generated by %s for %s@%s at %s.' % (
        __file__,
        os.environ['USER'],
        socket.gethostname(),
        time.strftime('%Y-%m-%d %H:%M:%S'))
  root = lxml.etree.XML("""<ChangeResourceRecordSetsRequest xmlns="%s">
                        <ChangeBatch>
                          <Comment>%s</Comment>
                          <Changes/>
                        </ChangeBatch>
                        </ChangeResourceRecordSetsRequest>""" % (
      R53_XMLNS, comment), parser=XML_PARSER)
  changesroot = root.find('.//{%s}Changes' % R53_XMLNS)
  old = normalize_rrs(old)
  new = normalize_rrs(new)
  oldset = set([lxml.etree.tostring(x).rstrip() for x in old])
  newset = set([lxml.etree.tostring(x).rstrip() for x in new])
  if oldset == newset:
      return None
  # look for removed elements
  for rrs in old:
    rrsst = lxml.etree.tostring(rrs).rstrip()
    if rrsst not in newset:
      log.debug("REMOVED:")
      log.debug(rrsst)
      change = lxml.etree.XML('<Change xmlns="%s"><Action>DELETE</Action></Change>' % R53_XMLNS, parser=XML_PARSER)
      change.append(rrs)
      changesroot.append(change)
  # look for added elements
  for rrs in new:
    rrsst = lxml.etree.tostring(rrs).rstrip()
    if rrsst not in oldset:
      log.debug("ADDED:")
      log.debug(rrsst)
      change = lxml.etree.XML('<Change xmlns="%s"><Action>CREATE</Action></Change>' % R53_XMLNS, parser=XML_PARSER)
      change.append(rrs)
      changesroot.append(change)
  return root
Diff two XML configs and return an object with changes to be written.

  Args: old, new: lxml.etree.Element (<ResourceRecordSets>).
  Returns: lxml.etree.ETree (<ChangeResourceRecordSetsRequest>) or None