def format_clear(self, entries, **options):
     subject = options["subject"]
     replyto_address = options["replyto_address"]
     from_address = options["from_address"]
     extra_headers = options["extra_headers"]
     
     if self.customer.useNewOutageEmailTemplate():
         try:
             raw_text = masterdb.SystemEmailTemplateContent.fetch(self.customer.brand, "notifyengine2.clear").raw_text
             raw_html = masterdb.SystemEmailTemplateContent.fetch(self.customer.brand, "notifyengine2.clear").raw_html
             text_template = None
             html_template = None
         except:
             text_template = EmailText.clear_template
             html_template = self.new_template
             raw_text = raw_html = None
     else:
          text_template = EmailText.clear_template
          html_template = self.clear_template
          raw_text = raw_html = None
          
     options['raw_template'] = raw_text
     text = self.format_message(text_template, entries, **options)
     options['raw_template'] = raw_html
     html = self.format_message(html_template, entries, **options)
     msg = buildEmail(from_address, self.contact_info, subject, text, html, replyto_address, extra_headers)
     return msg.as_string()
 def format_broadcast(self, entries, **options):
     subject = options["subject"]
     replyto_address = options["replyto_address"]
     from_address = options["from_address"]
     extra_headers = options["extra_headers"]
     
     text = TruncatedTextPlugin.format_broadcast(self, entries, **options)
     msg = buildEmail(from_address, self.contact_info, subject, text, "", replyto_address, extra_headers)
def validate_recurring_profiles():
    profiles = PaypalProfile.selectBy(status='active', type='recurring')
    logging.info("validating %d recurring profiles" % profiles.count())
    for profile in profiles:
        amt = sum([amount for desc, amount, item in profile.customer.getFees()])
        if amt != profile.amount:
            logging.error("profile id-%d doesn't match customer's fees!" % profile.id)
            msg = "Customer %d's PayPal recurring profile id-%d amount %.2f differs from their package price with fees %.2f"
            msg = msg % (profile.id, profile.customer.id, profile.amount, amt)
            body = utilities.buildEmail('*****@*****.**', '*****@*****.**', 'PayPal error!', msg, '')
            #try: utilities.sendEmail('*****@*****.**', '*****@*****.**', body)
            #except: pass
        else:
            logging.info("profile id-%d OK" % profile.id)
    def send_exception_email(self, status, url, data, user, headers, params):
        """Send an email with the error info to the admin.

        Uses TurboMail if installed and activated, otherwise tries to send
        email with the smtplib module. The SMTP settings can be configured
        with the following configuration settings:

        error_catcher.smtp_host   - Mail server to connect to
                                    (default 'localhost')
        error_catcher.smtp_user   - User name for SMTP authentication.
                                    If unset no SMTP login is performed.
        error_catcher.smtp_passwd - Password for SMTP authentication
        """

        if not self.sender_email or not  self.admin_email:
            log.exception('Configuration error: could not send error'
              'because sender and/or admin email address is not set.')
            raise RuntimeError

        subject =  '%d ERROR on the Server [%s]' % (status, self.running_environment)
        text = ERROR_MAIL_TMPL % dict(url=url, data=data, user=user, headers=headers, params=params)

        if has_turbomail and turbogears.config.get('mail.on'):
            msg = utilities.buildEmail(self.sender_email, self.admin_email, subject, text, '')
#            msg = turbomail.Message(self.sender_email, self.admin_email, subject)
#            msg.plain = text
#            turbomail.enqueue(msg)
            utilities.sendEmail(self.sender_email, self.admin_email, msg)

        else:
            from email.MIMEMultipart import MIMEMultipart
            from email.MIMEText import MIMEText
            from email.Utils import formatdate
            msg = MIMEMultipart()
            msg['From'] = self.sender_email
            msg['To'] = self.admin_email
            msg['Date'] = formatdate(localtime=True)
            msg['Subject'] = subject
            msg.attach(MIMEText(text))
            self._send_email_smtp(self.sender_email, self.admin_email,
              msg.as_string())
    def format_broadcast(self, entries, **options):
        subject = options["subject"]
        replyto_address = options["replyto_address"]
        from_address = options["from_address"]
        extra_headers = options["extra_headers"]
        
        if self.customer.useNewOutageEmailTemplate():
            try:
                text_template = None
                raw_template = masterdb.SystemEmailTemplateContent.fetch(self.customer.brand, "notifyengine2.broadcast").raw_text
            except:
                text_template = self.broadcast_template
                raw_template = ""
        else:
                text_template = self.broadcast_template
                raw_template = ""

        options['raw_template'] = raw_template
        text = self.format_message(text_template, entries, **options)
        msg = buildEmail(from_address, self.contact_info, subject, text, "", replyto_address, extra_headers)
        return msg.as_string()
    def run(self):
        report_requests_output_dir = turbogears.config.get('report_requests_output_dir', '/etc/report_requests')

        while True:
            time.sleep(5.0)

            if self.shutdown_flag.isSet():
                sys.exit()
            else:
                logging.info("Looking for reports")
                report_requests = masterdb.ReportRequest.selectBy(status='new')
                if not report_requests.count(): continue
                rr = report_requests[0]
                logging.info("Generating report request %s for %s" % (rr.id, rr.request_user.display_name))
                brand_name = rr.customer.brand.name if rr.customer.brand else "Panopta"
                tempdir = tempfile.mkdtemp()

                try:
                    rr.status = 'inprocess'

                    # Build the list of servers that the report covers
                    servers = set()
                    for mp in rr.monitor_points:
                        if mp.server.status == 'active': servers.add(mp.server)
                    for sr in rr.server_resources:
                        if sr.server.status == 'active': servers.add(sr.server)
                    servers = list(servers)
                    logging.info("%s: Found %s servers for report" % (rr.id, len(servers)))

                    # Get event history
                    all_outages = []
                    for s in servers:
                        for a in masterdb.ServerResourceAnomaly.select(AND(masterdb.ServerResourceAnomaly.q.server == s,
                                                                           masterdb.ServerResourceAnomaly.q.start_time >= rr.report_start_time,
                                                                           OR(masterdb.ServerResourceAnomaly.q.end_time == None, masterdb.ServerResourceAnomaly.q.end_time <= rr.report_end_time))):
                            all_outages.append(a)
                        for o in masterdb.ServerOutage.select(AND(masterdb.ServerOutage.q.server == s,
                                                                  IN(masterdb.ServerOutage.q.status, ['confirmed', 'resolved']),
                                                                  masterdb.ServerOutage.q.start_time >= rr.report_start_time,
                                                                  OR(masterdb.ServerOutage.q.end_time == None, masterdb.ServerOutage.q.end_time <= rr.report_end_time))):
                            all_outages.append(o)
                    all_outages.sort(key=lambda o: o.start_time, reverse=True)
                    logging.info("%s: Found %s outages" % (rr.id, len(all_outages)))

                    date_formatter = rr.request_user.contact.formatLocalTime 

                    # Get servers, monitor_points, agent_resources, snmp_resources
                    servers = {}
                    for mp in rr.monitor_points:
                        try: server = mp.server
                        except: continue
                        if server not in servers: servers[server] = set([mp])
                        else: servers[server].add(mp)
                    for sr in rr.server_resources:
                        try: server = sr.server
                        except: continue
                        if sr.monitor_point:
                            if server not in servers: servers[server] = set([sr.monitor_point])
                            else: servers[server].add(sr.monitor_point)
                    servers = servers.items()
                    servers.sort(key=lambda s: s[0].fqdn.lower()) 

                    # Generating server excel files
                    book_names = []
                    overview_data = []
                    for server, monitor_points in servers:
                        logging.info("%s: Generating Excel file for %s" % (rr.id, server.formatted_name))

                        downtime = "%.2f" % (server.getTotalDownTime(rr.report_start_time, rr.report_end_time) / 60.0)
                        downtime_with_excluded = "%.2f" % (server.getTotalDownTime(rr.report_start_time, 
                                                                                   rr.report_end_time,
                                                                                   with_excluded_outages=True) / 60.0)
                        availability = "%.2f%%" % (server.getAvailability(rr.report_start_time, 
                                                                          rr.report_end_time)[0] * 100.0)
                        availability_with_excluded = "%.2f%%" % (server.getAvailability(rr.report_start_time, 
                                                                                        rr.report_end_time,
                                                                                        with_excluded_outages=True)[0] * 100.0)
                        overview_data.append([server.fqdn, downtime, availability, downtime_with_excluded, availability_with_excluded, ""])

                        # Generating detailed check results sheet
                        logging.info("%s: Generating check result sheet" % rr.id)
                        book = tablib.Databook()
                        sheet = tablib.Dataset()
                        book.add_sheet(sheet)
                        sheet.title = "Detailed Check Results"
                        sheet.append(["%s Detailed Check Results" % server.fqdn, "", "", "", ""])
                        sheet.append(["", "", "", "", ""])

                        report_duration = (rr.report_end_time - rr.report_start_time).days
                        if report_duration > 31:
                            sheet.append(["Timestamp", "Location", "Service", "Average Duration (s)", ""])
                            num_records, results = masterdb.getCheckResults_summary(monitor_points, rr.report_start_time, rr.report_end_time)
                            for monitor_point, monitor_node, check_date, result in results:
                                sheet.append([check_date, monitor_node and monitor_node.name or "", monitor_point.service_type.name, 1, result])
                        else:
                            sheet.append(["Timestamp", "Location", "Service", "Result", "Duration (s)"])
                            num_records, results = masterdb.getCheckResults_new(monitor_points, rr.report_start_time, rr.report_end_time, batch_size=1440*35)
                            for monitor_point, monitor_node, check_time, result, result_duration in results:
                                sheet.append([date_formatter(check_time), monitor_node and monitor_node.name or "", monitor_point.service_type.name, result, result_duration])
                            
                        # Generating detailed agent server resource level results sheet
                        logging.info("%s: Generating agent metrics sheet" % rr.id)
                        agent_resources = masterdb.ServerResource.select(AND(masterdb.ServerResource.q.server == server,
                                                                             masterdb.ServerResource.q.check_method == "resource.agent.panopta"))
                        if agent_resources.count():
                            sheet = tablib.Dataset()                            
                            sheet.title = "Agent Metrics"
                            sheet.append(["%s Detailed Agent Resource Results" % server.fqdn, "", ""])
                            sheet.append(["", "", ""])
                            sheet.append(["Timestamp", "Resource", "Result"]) 
                            num_records, results = masterdb.getMultipleServerResourceLevelData(agent_resources, rr.report_start_time, rr.report_end_time, batch_size=1440*35)
                            for server_resource, check_time, result in results:
                                sheet.append([date_formatter(check_time), server_resource.name, result])
                            if results: book.add_sheet(sheet)

                        # Generating detailed snmp server resource level results sheet
                        logging.info("%s: Generating SNMP metrics sheet" % rr.id)
                        snmp_resources = masterdb.ServerResource.select(AND(masterdb.ServerResource.q.server == server,
                                                                            OR(masterdb.ServerResource.q.check_method == "resource.snmp",
                                                                               masterdb.ServerResource.q.check_method == "resource.snmp.child")))
                        if snmp_resources.count():
                            sheet = tablib.Dataset()                            
                            sheet.title = "SNMP Metrics"
                            sheet.append(["%s Detailed SNMP Resource Results" % server.fqdn, "", ""])
                            sheet.append(["", "", ""])
                            sheet.append(["Timestamp", "Resource", "Result"]) 
                            num_records, results = masterdb.getMultipleServerResourceLevelData(snmp_resources, rr.report_start_time, rr.report_end_time, batch_size=1440*35)
                            for server_resource, check_time, result in results:
                                sheet.append([date_formatter(check_time), server_resource.name, result])
                            if results: book.add_sheet(sheet)

                        # Save server excel file
                        book_name = server.fqdn[:30]
                        if book_name in book_names: book_name = "%s_%s" % (server.fqdn[:30], server.id)
                        book_names.append(book_name)
                        with open(os.path.join(tempdir, '%s.xls' % book_name), 'wb') as f: f.write(book.xls)

                    # Generating the Overview excel file
                    logging.info("%s: Generating overview spreadsheet" % rr.id)
                    book = tablib.Databook()
                    sheet = tablib.Dataset()
                    book.add_sheet(sheet)
                    sheet.title = 'Overview'
                    sheet.append(["%s Monitoring Report" % brand_name, "", "", "", "", ""])
                    sheet.append(["Report covers time period %s - %s" % (date_formatter(rr.report_start_time), date_formatter(rr.report_end_time)), "", "", "", "", ""])
                    sheet.append(["", "", "", "", "", ""])
                    sheet.append(["Overview", "", "", "", "", ""])
                    sheet.append(["Server", "Downtime (minutes)", "Availability", "Downtime w/ Excluded Outages (minutes)", "Availabilty w/ Excluded outages", ""])
                    for od in overview_data: sheet.append(od)                        
                    sheet.append(["", "", "", "", "", ""])
                    sheet.append(["Event History", "", "", "", "", ""])
                    sheet.append(["Server", "Excluded?", "Start Time", "End Time", "Services", "Reason"])
                    for outage in all_outages:
                        sheet.append([outage.server.fqdn if outage.server else outage.target.name, outage.exclude_from_availability and "Yes" or "No", date_formatter(outage.start_time), date_formatter(outage.end_time) if outage.end_time else "Ongoing", str(outage), outage.getOutageReasons("; ")])
                    with open(os.path.join(tempdir, 'Overview.xls'), 'wb') as f: f.write(book.xls)

                    # Create the zip file
                    logging.info("%s: Creating zip file" % rr.id)
                    output_dir = os.path.join(report_requests_output_dir, rr.customer.brand.textkey if rr.customer.brand else 'panopta')
                    if not os.path.exists(output_dir): os.makedirs(output_dir)
                    file_name = '%s-Report-%s-%s.zip' % (brand_name.replace(" ", "_"), datetime.now().strftime("%Y%m%d"), rr.id)
                    rr.file_name = file_name
                    output_file = os.path.join(output_dir, file_name)
                    os.popen("zip -r -j %s %s" % (output_file, tempdir))  
                    shutil.rmtree(tempdir)
                    logging.info("%s: Created report %s" % (rr.id, output_file))

                    zip_size = int(os.path.getsize(output_file)/1024./1024.)

                    # Send the report email
                    try:
                        from_address = turbogears.config.get('from_address', '*****@*****.**')
                        reply_to_address = turbogears.config.get('reply_to_address', '*****@*****.**')
                        bcc_address = turbogears.config.get('bcc_address', '*****@*****.**')
                        to_address = rr.email_address
                        title = rr.name or ("Your %s monitoring report" % brand_name)
                        if zip_size > ZIP_SIZE_CUTOFF:
                            # Generated file is too big, don't send as attachment
                            template = masterdb.SystemEmailTemplateContent.fetch(rr.customer.brand, "report_request_no_attachment")
                            text = template.text.safe_substitute({'from_address': from_address, 'to_address': to_address, 'brand': brand_name, 'report_request': rr})
                            html = template.html.safe_substitute({'from_address': from_address, 'to_address': to_address, 'brand': brand_name, 'report_request': rr})
                            msg = buildEmail(from_address, to_address, title, text, html, reply_to_address)
                        else:
                            # Generated file is not too big, include as attachment
                            template = masterdb.SystemEmailTemplateContent.fetch(rr.customer.brand, "report_request")
                            text = template.text.safe_substitute({'from_address': from_address, 'to_address': to_address, 'brand': brand_name, 'report_request': rr})
                            html = template.html.safe_substitute({'from_address': from_address, 'to_address': to_address, 'brand': brand_name, 'report_request': rr})
                            msg = buildEmail(from_address, to_address, title, text, html, reply_to_address, attachments=[output_file])
                        

                        username = turbogears.config.get("smtpuser", "")
                        password = turbogears.config.get("smtppass", "")
                        s = smtplib.SMTP(turbogears.config.get('smtpserver', 'localhost'))
                        if username and password: s.login(username, password)
                        s.sendmail(from_address, [to_address, bcc_address], msg.as_string())
                        s.quit()
                        logging.info("%s: Sent report to %s" % (rr.id, to_address))
                    except:
                       logging.exception("%s: ERROR SENDING REPORT REQUEST to %s" % (rr.id, to_address))

                    rr.status = 'finalizing'
                except:
                    logging.exception("ERROR GETTING REPORT REQUEST")
                    try: rr.status = 'error'
                    except: pass

                    # Send the error email
                    try:
                        reply_to_address = from_address = "*****@*****.**"
                        to_address = "*****@*****.**"
                        title = "Report Engine Adhoc Error for report %s" % rr.id
                        html = ""
                        text = "\n".join(traceback.format_tb(sys.exc_info()[2]))
                        msg = buildEmail(from_address, to_address, title, text, html, reply_to_address)
                        username = turbogears.config.get("smtpuser", "")
                        password = turbogears.config.get("smtppass", "")
                        s = smtplib.SMTP(turbogears.config.get('smtpserver', 'localhost'))
                        if username and password: s.login(username, password)
                        s.sendmail(from_address, [to_address], msg.as_string())
                        s.quit()
                    except:
                        pass
    def buildMessage(self, notice_type, channel, outages, anomalies, replyto_address, from_address):
        """
        Construct an alert message for the given list of outages and anomalies, to be sent to the given channel.

        General process is to construct fragments for each item to be reported on, then assemble them all
        together into the final message.  Need to take into consideration whether we're building text email,
        HTML email, short text email or SMS message, which is determined by the channel.
        """

        customer = channel.contact.customer
        brand = customer.brand
        brand_name = "Panopta"
        if brand and brand.isWhiteLabel():
            brand_name = brand.name

        shorttext_body = u""
        text_body = u""
        html_body = u""

        # Make a list of server names to notify, for use in the subject header
        items = outages + anomalies
        server_names = set()
        for o in outages:
            try:
                target = o.outage_target
            except:
                continue
            server_names.add(target.name)
        for a in anomalies:
            server_names.add(a.server.name)
        server_names = list(server_names)
        server_names.sort()

        # Construct the message body
        if channel.contact_type.textkey in ('sms', 'email.shorttext'):
            
            # Figure out the limit on the message length - shorter for SMS to avoid truncation
            length_limit = 148
            if channel.contact_type.textkey == 'email.shorttext': length_limit = 350

            # Build up a list of all of the things to notify about
            fragments = []
            for o in outages:
                notice_time = o.start_time
                if notice_type == ALL_CLEAR:
                    notice_time = o.end_time
                fragments.append(_("%(server_name)s %(notice_type)s at %(start_time)s: %(service_list)s") % \
                                     {'server_name': o.outage_target.name, 
                                      'notice_type': _(notice_type), 
                                      'start_time': channel.contact.formatLocalTime(notice_time, hide_same_day=1),
                                      'service_list': o.getServiceDisplayNames(join_str=',')})
            for a in anomalies:
                notice_time = a.start_time
                notice_desc = a.server_resource_threshold.getAnomalyDescription(short=1)
                if notice_type == ALL_CLEAR:
                    notice_time = a.end_time
                    notice_desc = a.server_resource_threshold.getAllClearDescription(short=1)
                fragments.append(_("%(server_name)s %(notice_type)s at %(start_time)s") % {'server_name': a.server.name, 
                                                                                           'notice_type': notice_desc,
                                                                                           'start_time': channel.contact.formatLocalTime(notice_time, hide_same_day=1)})

            # Complete the message by including as many fragments as possible while staying under 
            # the length limit.
            shorttext_body = fragments.pop(0)
            while fragments and (len(shorttext_body) + len(fragments[0]) + 2 < length_limit):
                shorttext_body += "; %s" % fragments.pop(0)
            if fragments:
                shorttext_body += _(" + %(count)s more") % {'count': len(fragments)}

        else:
            
            # Build a list of formatted things to report
            fragments = []
            for o in outages:
                fragments.append(self.buildOutageFragment(notice_type, o, customer, channel))
            for a in anomalies:
                fragments.append(self.buildAnomalyFragment(notice_type, a, customer, channel))
                                
            # Get the template fragments, in case the customer has customized them
            header = customer.getTemplateFragment('email.header')
            if notice_type == OUTAGE:
                intro = customer.getTemplateFragment('email.outage.notice.intro')
                conclusion = customer.getTemplateFragment('email.outage.notice.conclusion')
            elif notice_type == ALL_CLEAR:
                intro = customer.getTemplateFragment('email.outage.allclear.intro')
                conclusion = customer.getTemplateFragment('email.outage.allclear.conclusion')
            copyright = customer.getTemplateFragment('email.copyright')

            # Build the text rendering of the list, as we can't easily do this with interpolation
            alert_list = ""
            for (head, sub_list) in fragments:
                alert_list += " o %s\n" % head
                for line in sub_list:
                    alert_list += "   - %s\n" % line
                alert_list += "\n"

            # Build the text body
            if header: text_body += header.body_text + "\n\n"
            if intro: text_body += intro.body_text + "\n\n"

            template = _(unicode(outage_text_template))
            if notice_type == ALL_CLEAR: template = _(unicode(allclear_text_template))
            text_body += template % {'alerts': alert_list,
                                     'brand_name': brand_name}
            if conclusion: text_body += conclusion.body_text + "\n\n"
            if copyright: text_body += copyright.body_text

            if channel.contact_type.textkey == 'email.html':
                
                # Build the HTML body
                template_data = {'alerts': fragments,
                                 'brand_name': brand_name,
                                 'source': unicode(_(outage_html_template)) }
                if notice_type == ALL_CLEAR: template_data['source'] = unicode(_(allclear_html_template))

                if header: html_body += unicode(header.body_html) + u"\n\n"
                if intro: html_body += unicode(intro.body_html) + u"\n\n"

#                tmpl = MarkupTemplate(unicode(_(outage_html_template)))
#                stream = tmpl.generate(alerts=fragments, brand_name=brand_name)
#                frag = stream.render('xhtml')
#                frag = apply(kid.Template, [], template_data).serialize(encoding='utf-8')
                from kid import Template
                t=Template(unicode(_(outage_html_template)),
                           brand_name = brand_name,
                           alerts = fragments)
                frag = unicode(t.serialize(output='xhtml', encoding='utf-8'), 'utf8')

                html_body += frag
                if conclusion: html_body += conclusion.body_html + "\n\n"
                if copyright: html_body += copyright.body_html

        # Build the list of extra headers to embed in the message
        extra_headers = []
        if brand and brand.isWhiteLabel():
            extra_headers.append(('X-Panopta-PartnerKey', customer.partner_key))
            extra_headers.append(('X-Panopta-Package', customer.package.textkey))
        extra_headers.append(('X-Panopta-OutageStatus', notice_type==ALL_CLEAR and 'all-clear' or 'outage'))
        extra_headers.append(('X-Panopta-OutageId', replyto_address.split('@')[0]))  # Grab the email hash from the replyto address
        for server_name in server_names:
	    logging.critical("Adding server name %s" % server_name)
            extra_headers.append(('X-Panopta-ServerFQDN', server_name))
            
        # Build the subject header
        subject = "%s %s: %s" % (brand_name, _(notice_type), server_names.pop(0))
        while server_names and len(subject) < 80:
            subject += "; %s" % server_names.pop(0)
        if server_names:
            subject += _(" + %(count)s more") % {'count': len(server_names)}

        # Build the message object
        if channel.contact_type.textkey == 'sms':
            # If we're sending an SMS, just return the body - there's nothing else to be done
            return shorttext_body
        elif channel.contact_type.textkey == 'email.shorttext':
            msg = buildEmail(from_address, channel.email_address, subject, shorttext_body, "", replyto_address, extra_headers)
        elif channel.contact_type.textkey == 'email.text':
            msg = buildEmail(from_address, channel.email_address, subject, text_body, "", replyto_address, extra_headers)
        else:
            msg = buildEmail(from_address, channel.email_address, subject, text_body, html_body, replyto_address, extra_headers)

        return msg.as_string()
def sendReport(contact_channel, start, end, title, frequency, server_groups, include_all_servers, show_average_response_time):

    contact = contact_channel.contact
    customer = contact.customer
    
    overall_avail = 0.
    total_outages = 0
    total_downtime = timedelta(seconds=0)
    num_servers = 0
    servers = []

    server_groups.sort(key=operator.attrgetter('nested_name'))

    for group in server_groups:

        group_servers = []
        for server in group.compound_services + group.servers:
            if server.status != 'active': continue
            (avail, outages) = server.getAvailability(start, end)
            overall_avail += avail
            outage_details = {}

            # Extract the availability information from the list of outages
            for (mp, num_outages, service_avail, downtime) in outages:
                total_outages += num_outages
                total_downtime += downtime
                avail_string = availabilityFormatString(service_avail*100., customer) % (100.*service_avail)

                if mp: 
                    key = mp.id
                    mp_name = mp.service_type.name
                    if mp.name: mp_name += " : %s" % mp.name
                else: 
                    key = "cs%s" % server.id
                    mp_name = ""

                if mp and show_average_response_time:
                    avg_response_time = customer.getAvailabilityFormat() % mp.getAverageResponseTime(start, end)
                else:
                    avg_response_time = ''
                outage_details[key] = (mp_name, num_outages, avail_string, downtime, avg_response_time)

            # Get average response time information for any servers that didn't have outages
            if server.__class__.__name__ != 'CompoundService':
                for mp in server.active_monitor_points:
                    if not outage_details.has_key(mp.id):
                        if show_average_response_time:
                            avg_response_time = customer.getAvailabilityFormat() % mp.getAverageResponseTime(start, end)
                        else:
                            avg_response_time = 0.
                        mp_name = mp.service_type.name
                        if mp.name: mp_name += unicode(" - %s" % mp.name)
                        outage_details[mp.id] = (mp_name,
                                                 0,
                                                 availabilityFormatString(100., customer) % 100., 
                                                 timedelta(hours=0), 
                                                 avg_response_time)
                    
            if include_all_servers or (avail != 1.0 or outages):
                # Only show a specific server if there's been an outage, unless the customer has 
                # chosen to show all servers
                avail_string = availabilityFormatString(avail*100., customer) % (100.*avail)
                group_servers.append((server, avail_string, sorted(outage_details.values())))
    
            num_servers += 1
        if group_servers: servers.append((group, group_servers))

    if num_servers:
        overall_avail = overall_avail*100./(1.0*num_servers)
        format_str = availabilityFormatString(overall_avail, customer)
        overall_avail = format_str % overall_avail
    else:
        # Don't send the email if they have no servers to report on
        return

    if total_outages:
        average_downtime = formatTimedelta(total_downtime/total_outages)
    else:
        average_downtime = unicode(_("N/A"))
    total_downtime = formatTimedelta(total_downtime)
    
    report_type = string.capwords(frequency)
   
    from_address = turbogears.config.get('from_address', '*****@*****.**')
    reply_to_address = turbogears.config.get('reply_to_address', '*****@*****.**')
    bcc_address = turbogears.config.get('bcc_address', '*****@*****.**')
    to_address = contact_channel.email_address

    if customer.brand and customer.brand.isWhiteLabel() and customer.brand.support_email_address:
        from_address = customer.brand.support_email_address
        reply_to_address = customer.brand.support_email_address

    # Get the fragments to use for building the report content
    html_start = customer.getTemplateFragment('email.report.header')
    header = customer.getTemplateFragment('email.header')
    intro = customer.getTemplateFragment('email.report.intro')
    conclusion = customer.getTemplateFragment('email.report.conclusion')
    copyright = customer.getTemplateFragment('email.copyright')

    # Generate the HTML version of the report - Genshi for the actual report body,
    # glued together with other fragments
    tmpl = MarkupTemplate(open("ReportTemplate.html"))
    tmpl.filters.insert(0, Translator(_))
    stream = tmpl.generate(overall_avail=overall_avail,
                           controlpanel_url=customer.getControlPanelURL().rstrip('/'),
                           servers=servers, 
                           title=title,
                           report_type=report_type,
                           total_downtime=total_downtime,
                           average_downtime=average_downtime, 
                           total_outages=total_outages, 
                           num_servers=num_servers,
                           formatTimedelta=formatTimedelta,
                           show_average_response_time=show_average_response_time)
    
    report_html = stream.render('html', None)

    if html_start:
        html = html_start.body_html
    else:
        html = HTML_HEADER

    if header: html += header.body_html 
    html += "<h1>%s</h1>" % title
    if intro: html += intro.body_html
    html += report_html
    if conclusion: html += conclusion.body_html
    if copyright: html += copyright.body_html

    # Generate the text version of the report
    text_tmpl = TextTemplate(unicode(open("ReportTemplate.txt").read(), 'utf8'))
    text_tmpl.filters.insert(0, Translator(_))
    text_stream = text_tmpl.generate(overall_avail=overall_avail,
                                     servers=servers,
                                     title=title,
                                     report_type=report_type,
                                     total_downtime=total_downtime,
                                     average_downtime=average_downtime, 
                                     total_outages=total_outages,
                                     num_servers=len(customer.servers),
                                     formatTimedelta=formatTimedelta,
                                     show_average_response_time=show_average_response_time,
                                     center=center)
    report_text = text_stream.render('text')
    text = u""
    if header: text += header.body_text + "\n"
    text += "\n%s\n===============================================================================\n\n" % title
    if intro: text += intro.body_text + "\n\n"

    text += report_text + "\n\n"
    if conclusion: text += conclusion.body_text + "\n\n"
    if copyright: text += copyright.body_text + "\n"

    msg = buildEmail(from_address, to_address, title, text, html, reply_to_address)

#     msg = MIMEMultipart('related')
#     msg['Subject'] = title
#     msg['From'] = from_address
#     msg['To'] = to_address
#     msg['Reply-To'] = reply_to_address
#     msg.preamble = "This is a multi-part message in MIME format."
   
#     # Encapsulate the plain and HTML versions of the message body in an
#     # 'alternative' part, so message agents can decide which they want to display.
#     msgAlternative = MIMEMultipart('alternative')
#     msg.attach(msgAlternative)
                
#     msg_text = MIMEText(text)
#     msgAlternative.attach(msg_text)
    
#     msgAlternative.attach(MIMEText(html, 'html'))
    
    username = turbogears.config.get("smtpuser", "")
    password = turbogears.config.get("smtppass", "")
    
    s = smtplib.SMTP(turbogears.config.get('smtpserver', 'localhost'))
    if username and password:
        s.login(username, password)
    s.sendmail(from_address, [to_address, bcc_address], msg.as_string())
    s.quit()
    logging.info("Sent report to %s" % to_address)
                title = unicode(_("%(brand_name)s Monthly Availability Report for %(month)s") % {'brand_name': brand_name, 
                                                                                                 'month': prev_day.strftime("%B, %Y")})
                new_end_time = end + timedelta(days=days_in_next_month)
            else:
                continue
    
            logging.info("Generating %s report for %s" % (frequency, contact_channel.email_address))
            if customer.status == 'active':
                sendReport(contact_channel, start, end, title, frequency, report.getServerGroups(), 
                           report.include_all_servers, report.show_average_response_time)
            report.next_end_time = new_end_time
            
        except:
            if contact_channel: logging.exception("ERROR SENDING REPORT to %s" % contact_channel.email_address)
            else: logging.exception("ERROR GETTING REPORT")

            try:
                reply_to_address = from_address = "*****@*****.**"
                to_address = "*****@*****.**"
                title = "Report Engine Error"
                html = text = traceback.format_tb(sys.exc_info()[2])
                msg = buildEmail(from_address, to_address, title, text, html, reply_to_address)
                username = turbogears.config.get("smtpuser", "")
                password = turbogears.config.get("smtppass", "")
                s = smtplib.SMTP(turbogears.config.get('smtpserver', 'localhost'))
                if username and password: s.login(username, password)
                s.sendmail(from_address, [to_address], msg.as_string())
                s.quit()
            except:
                pass