diff --git a/releasenotes/notes/email-notification-by-internal-tool-08910ab2247c3864.yaml b/releasenotes/notes/email-notification-by-internal-tool-08910ab2247c3864.yaml
new file mode 100644
index 000000000..20efe51c4
--- /dev/null
+++ b/releasenotes/notes/email-notification-by-internal-tool-08910ab2247c3864.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Currently the email subscription in Zaqar relay on the third part
+ tools, such as "sendmail". It means that deployer should install
+ it out of Zaqar. If he forgets, Zaqar will raise internal error.
+ This work let Zaqar support email subscription by itself using
+ the ``smtp`` python library.
diff --git a/samples/zaqar/sendmail.py b/samples/zaqar/sendmail.py
new file mode 100644
index 000000000..ff172189e
--- /dev/null
+++ b/samples/zaqar/sendmail.py
@@ -0,0 +1,228 @@
+# Copyright (c) 2018 Ustack, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy
+# of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from email.mime.text import MIMEText
+from email.parser import Parser
+import json
+import smtplib
+import sys
+
+from keystoneauth1 import loading
+from keystoneauth1 import session as ks_session
+from oslo_config import cfg
+import requests
+import retrying
+
+KUNKA_SERVICE_TYPE = 'portal'
+
+"""KUNKA_CORP_INFO_PATH is an API for obtaining information from the database
+(such as /api/email/corporation-info), and the returned information
+is the value of the field in the mail template. It is connected after
+the ip:port which is the database connection information. ip:port
+like 127.0.0.1:3306, or other"""
+KUNKA_CORP_INFO_PATH = 'Your API address'
+
+# The information is relatively sensitive, suggesting encrypted transmission,
+# or stored in the database to return.When "use_ssl" is False, the "port" is
+# 25, otherwise, the "port" is 465 in using SSL type.
+mail_info = {
+ "from": "youremail@youremail.com",
+ "hostname": "yourSMTP_serverAddr",
+ "username": "yourSMTP_server-username",
+ "password": "Authorization_code",
+ "port": 25,
+ "use_ssl": False
+}
+
+# It's a HTML mail template,and can be changed as needed
+mail_body = u"""
+
+
+
+
+

+
+
+
+
+
+
+
+ ///////////////////
+ Respected
+ {corp_name}user
+ ///////////////////
+
+
+
{corp_name}—
+ {home_link}
+
+
+
+"""
+
+mail_confirm_link = u"""
+Your mailbox will be used for receiving system notifications.
+If you confirm, click the following link:
+Activation link
+"""
+
+mail_alarm_info = u"""
+ Your alarm information is as follows:{reason}
+ Alarm level:{severity}
+ Alarm name:{alarm_name}
+ Alarm ID :{alarm_id}
+"""
+
+
+def prepare_conf():
+ cfg.CONF(project='zaqar')
+ loading.register_auth_conf_options(cfg.CONF, 'keystone_authtoken')
+
+
+def get_admin_session():
+ auth_plugin = \
+ loading.load_auth_from_conf_options(cfg.CONF, 'keystone_authtoken')
+ return ks_session.Session(auth=auth_plugin)
+
+
+def get_endpoint(session, service_type, interface='internal'):
+ return session.get_endpoint(service_type=service_type,
+ interface=interface)
+
+
+@retrying.retry(stop_max_attempt_number=3)
+def get_corp_info(session):
+ kunka_endpoint = get_endpoint(session, KUNKA_SERVICE_TYPE)
+ kunka_url = kunka_endpoint + KUNKA_CORP_INFO_PATH
+
+ res = None
+ res = requests.get(kunka_url)
+
+ corp_info = res.json()
+ return {"corp_name": corp_info['corporationName'],
+ "logo_url": corp_info['emailLogoUrl'],
+ "home_link": corp_info['homeUrl'],
+ "from": corp_info['from']}
+
+
+def generate_msg(subbody, to, from_, subject, **kwargs):
+ payload = mail_body.format(confirm_or_alarm=subbody, **kwargs)
+ msg = MIMEText(payload.encode('utf-8'), 'html', 'utf-8')
+ msg['subject'] = subject
+ msg['from'] = from_
+ msg['to'] = to
+
+ return msg
+
+
+def generate_subbody(subbody, **kwargs):
+ return subbody.format(**kwargs)
+
+
+def get_confirm_link(str_):
+ return str_.split('below: ')[-1]
+
+
+def prepare_msg(msg_str):
+ headers = Parser().parsestr(msg_str)
+ payload = headers.get_payload()
+
+ msg_subject = headers['subject']
+ if not headers['subject']:
+ alarm_info = json.loads(payload)['body']
+ subject = msg_subject + alarm_info['alarm_name']
+ template = generate_subbody(mail_alarm_info,
+ reason=alarm_info['reason'],
+ severity=alarm_info['severity'],
+ alarm_name=alarm_info['alarm_name'],
+ alarm_id=alarm_info['alarm_id'])
+ else:
+ subject = msg_subject
+ template = generate_subbody(mail_confirm_link,
+ confirm_link=get_confirm_link(payload))
+
+ session = get_admin_session()
+ corp_info = get_corp_info(session)
+
+ msg = generate_msg(
+ template, headers['to'],
+ corp_info['from'], subject, logo_url=corp_info['logo_url'],
+ corp_name=corp_info['corp_name'], home_link=corp_info['home_link'])
+
+ return msg
+
+
+@retrying.retry(stop_max_attempt_number=3)
+def send_it(msg):
+ # if "use_ssl" is True, the "port" is 465 in using SSL type,
+ # or other SSL port.
+ if mail_info['use_ssl']:
+ sender = smtplib.SMTP_SSL(mail_info["hostname"], mail_info['port'])
+ else:
+ sender = smtplib.SMTP(mail_info["hostname"], mail_info['port'])
+ sender.set_debuglevel(1)
+
+ sender.ehlo(mail_info["hostname"])
+ try:
+ sender.login(mail_info["username"], mail_info["password"])
+ except smtplib.SMTPException:
+ print("Error: Failed to connect to the SMTP service")
+ sender.sendmail(msg['from'], msg['to'], msg.as_string())
+
+
+def send_email(msg_str):
+ prepare_conf()
+ send_it(prepare_msg(msg_str))
+
+
+if __name__ == '__main__':
+ send_email(''.join(sys.stdin.readlines()))
diff --git a/zaqar/conf/notification.py b/zaqar/conf/notification.py
index 38653ce1b..e1a7e8f7a 100644
--- a/zaqar/conf/notification.py
+++ b/zaqar/conf/notification.py
@@ -14,6 +14,39 @@
from oslo_config import cfg
+smtp_mode = cfg.StrOpt(
+ 'smtp_mode', default='third_part',
+ choices=('third_part', 'self_local'),
+ help='There are two values can be chosen: third_part or '
+ 'self_local. third_part means Zaqar will use the tools '
+ 'from config option smtp_commnd. self_local means the '
+ 'smtp python library will be used.')
+
+
+smtp_host = cfg.HostAddressOpt(
+ 'smtp_host',
+ help='The host IP for the email system. It should be '
+ 'set when smtp_mode is set to self_local.')
+
+
+smtp_port = cfg.PortOpt(
+ 'smtp_port',
+ help='The port for the email system. It should be set when '
+ 'smtp_mode is set to self_local.')
+
+
+smtp_user_name = cfg.StrOpt(
+ 'smtp_user_name',
+ help='The user name for the email system to login. It should '
+ 'be set when smtp_mode is set to self_local.')
+
+
+smtp_user_password = cfg.StrOpt(
+ 'smtp_user_password',
+ help='The user password for the email system to login. It '
+ 'should be set when smtp_mode is set to self_local.')
+
+
smtp_command = cfg.StrOpt(
'smtp_command', default='/usr/sbin/sendmail -t -oi',
help=(
@@ -76,6 +109,11 @@ unsubscribe_confirmation_email_template = cfg.DictOpt(
GROUP_NAME = 'notification'
ALL_OPTS = [
+ smtp_mode,
+ smtp_host,
+ smtp_port,
+ smtp_user_name,
+ smtp_user_password,
smtp_command,
max_notifier_workers,
require_confirmation,
diff --git a/zaqar/notification/tasks/mailto.py b/zaqar/notification/tasks/mailto.py
index 1691b6b70..3df4000e0 100644
--- a/zaqar/notification/tasks/mailto.py
+++ b/zaqar/notification/tasks/mailto.py
@@ -16,6 +16,7 @@
from email.mime import text
import json
from six.moves import urllib_parse
+import smtplib
import subprocess
from oslo_log import log as logging
@@ -67,8 +68,6 @@ class MailtoTask(object):
conf_n = kwargs.get('conf').notification
try:
for message in messages:
- p = subprocess.Popen(conf_n.smtp_command.split(' '),
- stdin=subprocess.PIPE)
# Send confirmation email to subscriber.
if (message.get('Message_Type') ==
MessageType.SubscriptionConfirmation.name):
@@ -98,7 +97,23 @@ class MailtoTask(object):
msg["from"] = subscription['options'].get('from', '')
subject_opt = subscription['options'].get('subject', '')
msg["subject"] = params.get('subject', subject_opt)
- p.communicate(msg.as_string())
+ if conf_n.smtp_mode == 'third_part':
+ p = subprocess.Popen(conf_n.smtp_command.split(' '),
+ stdin=subprocess.PIPE)
+ p.communicate(msg.as_string())
+ elif conf_n.smtp_mode == 'self_local':
+ sender = smtplib.SMTP_SSL(conf_n.smtp_host,
+ conf_n.smtp_port)
+ sender.set_debuglevel(1)
+
+ sender.ehlo(conf_n.smtp_host)
+ try:
+ sender.login(conf_n.smtp_user_name,
+ conf_n.smtp_user_password)
+ except smtplib.SMTPException:
+ LOG.error("Failed to connect to the SMTP service")
+ continue
+ sender.sendmail(msg['from'], msg['to'], msg.as_string())
LOG.debug("Send mail successfully: %s", msg.as_string())
except OSError as err:
LOG.exception('Failed to create process for sendmail, '
diff --git a/zaqar/tests/unit/notification/test_notifier.py b/zaqar/tests/unit/notification/test_notifier.py
index 9c161e73f..b2ced9656 100644
--- a/zaqar/tests/unit/notification/test_notifier.py
+++ b/zaqar/tests/unit/notification/test_notifier.py
@@ -209,6 +209,7 @@ class NotifierTest(testing.TestBase):
queue_ctlr.get = mock.Mock(return_value={})
driver = notifier.NotifierDriver(subscription_controller=ctlr,
queue_controller=queue_ctlr)
+ ctlr.driver.conf.notification.smtp_mode = 'third_part'
called = set()
msg = ('Content-Type: text/plain; charset="us-ascii"\n'
'MIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nto:'