Add authorization by service token to the rescheduling script

Closes-bug: #1275652
Closes-bug: #1322221

Change-Id: I759f1330c670923f5b1bad846eff922a8bb25868
This commit is contained in:
Sergey Vasilenko 2014-07-23 16:39:28 +04:00 committed by Vladimir Kuklin
parent 6ee225b3e1
commit 7dd02c8869
3 changed files with 134 additions and 36 deletions

View File

@ -36,6 +36,7 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin
OCF_RESKEY_binary_default="neutron-dhcp-agent"
OCF_RESKEY_config_default="/etc/neutron/neutron.conf"
OCF_RESKEY_keystone_config_default="/etc/keystone/keystone.conf"
OCF_RESKEY_plugin_config_default="/etc/neutron/dhcp_agent.ini"
OCF_RESKEY_log_file_default="/var/log/neutron/dhcp-agent.log"
OCF_RESKEY_user_default="neutron"
@ -53,6 +54,7 @@ OCF_RESKEY_debug_default='false'
: ${OCF_RESKEY_tenant=${OCF_RESKEY_tenant_default}}
: ${OCF_RESKEY_binary=${OCF_RESKEY_binary_default}}
: ${OCF_RESKEY_config=${OCF_RESKEY_config_default}}
: ${OCF_RESKEY_keystone_config=${OCF_RESKEY_keystone_config_default}}
: ${OCF_RESKEY_plugin_config=${OCF_RESKEY_plugin_config_default}}
: ${OCF_RESKEY_user=${OCF_RESKEY_user_default}}
: ${OCF_RESKEY_pid=${OCF_RESKEY_pid_default}}
@ -110,6 +112,14 @@ Location of the OpenStack Neutron Service (neutron-server) configuration file
<content type="string" default="${OCF_RESKEY_config_default}" />
</parameter>
<parameter name="keystone_config" unique="0" required="0">
<longdesc lang="en">
Location of the Keystone configuration file
</longdesc>
<shortdesc lang="en">OpenStack Keystone config file</shortdesc>
<content type="string" default="${OCF_RESKEY_keystone_config_default}" />
</parameter>
<parameter name="plugin_config" unique="0" required="0">
<longdesc lang="en">
Location of the OpenStack DHCP Service (${OCF_RESKEY_binary}) configuration file
@ -234,6 +244,15 @@ neutron_dhcp_agent_validate() {
true
}
setup_auth() {
AUTH_TOKEN=""
if [[ -f $OCF_RESKEY_keystone_config ]] ; then
AUTH_TOKEN=$(grep -v '#' $OCF_RESKEY_keystone_config | grep -i 'admin_token\s*=\s*' | awk -F'=' '{print $2}')
fi
true
}
neutron_dhcp_agent_status() {
local pid
@ -371,8 +390,14 @@ neutron_dhcp_agent_start() {
sleep 3
done
# setup token-based authentication if it possible
AUTH_TAIL=""
if [[ -n "$AUTH_TOKEN" ]] ; then
AUTH_TAIL="--admin-auth-url=${OCF_RESKEY_os_auth_url} --auth-token=${AUTH_TOKEN}"
fi
# detach deffered rescheduling procedure
bash -c "sleep 33 ; q-agent-cleanup.py --agent=dhcp --reschedule --remove-dead 2>&1 >> /var/log/neutron/rescheduling.log" &
bash -c "sleep 33 ; q-agent-cleanup.py --agent=dhcp --reschedule --remove-dead ${AUTH_TAIL} 2>&1 >> /var/log/neutron/rescheduling.log" &
ocf_log info "OpenStack DHCP Server (${OCF_RESKEY_binary}) started"
return $OCF_SUCCESS
@ -454,6 +479,7 @@ esac
# Anything except meta-data and help must pass validation
neutron_dhcp_agent_validate || exit $?
setup_auth || exit $?
# What kind of method was invoked?
case "$1" in

View File

@ -36,6 +36,7 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin
OCF_RESKEY_binary_default="neutron-l3-agent"
OCF_RESKEY_config_default="/etc/neutron/neutron.conf"
OCF_RESKEY_keystone_config_default="/etc/keystone/keystone.conf"
OCF_RESKEY_plugin_config_default="/etc/neutron/l3_agent.ini"
OCF_RESKEY_log_file_default="/var/log/neutron/l3-agent.log"
OCF_RESKEY_user_default="neutron"
@ -53,6 +54,7 @@ OCF_RESKEY_debug_default=false
: ${OCF_RESKEY_tenant=${OCF_RESKEY_tenant_default}}
: ${OCF_RESKEY_binary=${OCF_RESKEY_binary_default}}
: ${OCF_RESKEY_config=${OCF_RESKEY_config_default}}
: ${OCF_RESKEY_keystone_config=${OCF_RESKEY_keystone_config_default}}
: ${OCF_RESKEY_plugin_config=${OCF_RESKEY_plugin_config_default}}
: ${OCF_RESKEY_user=${OCF_RESKEY_user_default}}
: ${OCF_RESKEY_pid=${OCF_RESKEY_pid_default}}
@ -61,7 +63,6 @@ OCF_RESKEY_debug_default=false
: ${OCF_RESKEY_log_file=${OCF_RESKEY_log_file_default}}
#######################################################################
usage() {
@ -111,6 +112,14 @@ Location of the OpenStack Router (neutron-server) configuration file
<content type="string" default="${OCF_RESKEY_config_default}" />
</parameter>
<parameter name="keystone_config" unique="0" required="0">
<longdesc lang="en">
Location of the Keystone configuration file
</longdesc>
<shortdesc lang="en">OpenStack Keystone config file</shortdesc>
<content type="string" default="${OCF_RESKEY_keystone_config_default}" />
</parameter>
<parameter name="plugin_config" unique="0" required="0">
<longdesc lang="en">
Location of the OpenStack L3 Service (neutron-l3-agent) configuration file
@ -267,6 +276,15 @@ neutron_l3_agent_validate() {
true
}
setup_auth() {
AUTH_TOKEN=""
if [[ -f $OCF_RESKEY_keystone_config ]] ; then
AUTH_TOKEN=$(grep -v '#' $OCF_RESKEY_keystone_config | grep -i 'admin_token\s*=\s*' | awk -F'=' '{print $2}')
fi
true
}
neutron_l3_agent_status() {
local pid
@ -407,8 +425,14 @@ neutron_l3_agent_start() {
sleep 3
done
# setup token-based authentication if it possible
AUTH_TAIL=""
if [[ -n "$AUTH_TOKEN" ]] ; then
AUTH_TAIL="--admin-auth-url=${OCF_RESKEY_os_auth_url} --auth-token=${AUTH_TOKEN}"
fi
# detach deferred rescheduling procedure
bash -c "sleep 33 ; q-agent-cleanup.py --agent=l3 --reschedule --remove-dead 2>&1 >> /var/log/neutron/rescheduling.log " &
bash -c "sleep 33 ; q-agent-cleanup.py --agent=l3 --reschedule --remove-dead ${AUTH_TAIL} 2>&1 >> /var/log/neutron/rescheduling.log " &
fuel-fdb-cleaner --ssh-keyfile /root/.ssh/id_rsa_neutron -l /var/log/neutron/fdb-cleaner.log
ocf_log info "OpenStack Router (neutron-l3-agent) started"
@ -490,6 +514,7 @@ esac
# Anything except meta-data and help must pass validation
neutron_l3_agent_validate || exit $?
setup_auth || exit $?
# What kind of method was invoked?
case "$1" in

View File

@ -3,6 +3,8 @@ import re
import time
import os
import sys
import random
import string
import json
import argparse
import logging
@ -12,11 +14,13 @@ import subprocess
import StringIO
from neutronclient.neutron import client as q_client
from keystoneclient.v2_0 import client as ks_client
from keystoneclient.apiclient.exceptions import NotFound as ks_NotFound
LOG_NAME='q-agent-cleanup'
LOG_NAME = 'q-agent-cleanup'
API_VER = '2.0'
PORT_ID_PART_LEN=11
PORT_ID_PART_LEN = 11
TMP_USER_NAME = 'tmp_neutron_admin'
def get_authconfig(cfg_file):
@ -88,33 +92,80 @@ class NeutronCleaner(object):
self._token = None
self._keystone = None
self._client = None
self._need_cleanup_tmp_admin = False
def __del__(self):
if self._need_cleanup_tmp_admin and self._keystone and self._keystone.username:
try:
self._keystone.users.delete(self._keystone.users.find(username=self._keystone.username))
except:
# if we get exception while cleaning temporary account -- nothing harm
pass
def generate_random_passwd(self, length=13):
chars = string.ascii_letters + string.digits + '!@#$%^&*()'
random.seed = (os.urandom(1024))
return ''.join(random.choice(chars) for i in range(length))
@property
def keystone(self):
if self._keystone is None:
ret_count = self.options.get('retries',1)
ret_count = self.options.get('retries', 1)
tmp_passwd = self.generate_random_passwd()
while True:
if ret_count <= 0 :
if ret_count <= 0:
self.log.error(">>> Keystone error: no more retries for connect to keystone server.")
sys.exit(1)
try:
self._keystone = ks_client.Client(
username=self.auth_config['OS_USERNAME'],
password=self.auth_config['OS_PASSWORD'],
tenant_name=self.auth_config['OS_TENANT_NAME'],
auth_url=self.auth_config['OS_AUTH_URL'],
)
a_token = self.options.get('auth-token')
a_url = self.options.get('admin-auth-url')
if a_token and a_url:
self.log.debug("Authentication by predefined token.")
# create keystone instance, authorized by service token
ks = ks_client.Client(
token=a_token,
endpoint=a_url,
)
service_tenant = ks.tenants.find(name='services')
auth_url = ks.endpoints.find(
service_id=ks.services.find(type='identity').id
).internalurl
# find and re-create temporary rescheduling-admin user with random password
try:
user = ks.users.find(username=TMP_USER_NAME)
ks.users.delete(user)
except ks_NotFound:
# user not found, it's OK
pass
user = ks.users.create(TMP_USER_NAME, tmp_passwd, tenant_id=service_tenant.id)
ks.roles.add_user_role(user, ks.roles.find(name='admin'), service_tenant)
# authenticate newly-created tmp neutron admin
self._keystone = ks_client.Client(
username=user.username,
password=tmp_passwd,
tenant_id=user.tenantId,
auth_url=auth_url,
)
self._need_cleanup_tmp_admin = True
else:
self.log.debug("Authentication by given credentionals.")
self._keystone = ks_client.Client(
username=self.auth_config['OS_USERNAME'],
password=self.auth_config['OS_PASSWORD'],
tenant_name=self.auth_config['OS_TENANT_NAME'],
auth_url=self.auth_config['OS_AUTH_URL'],
)
break
except Exception as e:
errmsg = e.message.strip()
errmsg = str(e.message).strip() # str() need, because keystone may use int as message in exception
if re.search(r"Connection\s+refused$", errmsg, re.I) or \
re.search(r"Connection\s+timed\s+out$", errmsg, re.I) or\
re.search(r"Lost\s+connection\s+to\s+MySQL\s+server", errmsg, re.I) or\
re.search(r"Service\s+Unavailable$", errmsg, re.I) or\
re.search(r"'*NoneType'*\s+object\s+has\s+no\s+attribute\s+'*__getitem__'*$", errmsg, re.I) or \
re.search(r"No\s+route\s+to\s+host$", errmsg, re.I):
self.log.info(">>> Can't connect to {0}, wait for server ready...".format(self.auth_config['OS_AUTH_URL']))
time.sleep(self.options.sleep)
self.log.info(">>> Can't connect to {0}, wait for server ready...".format(self.auth_config['OS_AUTH_URL']))
time.sleep(self.options.sleep)
else:
self.log.error(">>> Keystone error:\n{0}".format(e.message))
raise e
@ -124,6 +175,7 @@ class NeutronCleaner(object):
def token(self):
if self._token is None:
self._token = self._keystone.auth_token
#self.log.debug("Auth_token: '{0}'".format(self._token))
#todo: Validate existing token
return self._token
@ -132,7 +184,9 @@ class NeutronCleaner(object):
if self._client is None:
self._client = q_client.Client(
API_VER,
endpoint_url=self.keystone.service_catalog.url_for(service_type='network'),
endpoint_url=self.keystone.endpoints.find(
service_id=self.keystone.services.find(type='network').id
).adminurl,
token=self.token,
)
return self._client
@ -140,21 +194,21 @@ class NeutronCleaner(object):
def _neutron_API_call(self, method, *args):
ret_count = self.options.get('retries')
while True:
if ret_count <= 0 :
if ret_count <= 0:
self.log.error("Q-server error: no more retries for connect to server.")
return []
try:
rv = method (*args)
break
except Exception as e:
errmsg = e.message.strip()
errmsg = str(e.message).strip()
if re.search(r"Connection\s+refused", errmsg, re.I) or\
re.search(r"Connection\s+timed\s+out", errmsg, re.I) or\
re.search(r"Lost\s+connection\s+to\s+MySQL\s+server", errmsg, re.I) or\
re.search(r"503\s+Service\s+Unavailable", errmsg, re.I) or\
re.search(r"No\s+route\s+to\s+host", errmsg, re.I):
self.log.info("Can't connect to {0}, wait for server ready...".format(self.keystone.service_catalog.url_for(service_type='network')))
time.sleep(self.options.sleep)
self.log.info("Can't connect to {0}, wait for server ready...".format(self.keystone.service_catalog.url_for(service_type='network')))
time.sleep(self.options.sleep)
else:
self.log.error("Neutron error:\n{0}".format(e.message))
raise e
@ -204,7 +258,7 @@ class NeutronCleaner(object):
)
rc = process.wait()
if rc != 0:
self.log.error("ERROR (rc={0}) while execution {1}".format(rc,' '.join(cmd)))
self.log.error("ERROR (rc={0}) while execution {1}".format(rc, ' '.join(cmd)))
return []
# filter namespaces by given agent type
netns = []
@ -218,7 +272,7 @@ class NeutronCleaner(object):
def __collect_ports_for_namespace(self, ns):
cmd = self.CMD__ip_netns_exec[:]
cmd.extend([ns,'ip','l','show'])
cmd.extend([ns, 'ip', 'l', 'show'])
self.log.debug("Execute command '{0}'".format(' '.join(cmd)))
process = subprocess.Popen(
cmd,
@ -228,7 +282,7 @@ class NeutronCleaner(object):
)
rc = process.wait()
if rc != 0:
self.log.error("ERROR (rc={0}) while execution {1}".format(rc,' '.join(cmd)))
self.log.error("ERROR (rc={0}) while execution {1}".format(rc, ' '.join(cmd)))
return []
ports = []
stdout = process.communicate()[0]
@ -269,11 +323,9 @@ class NeutronCleaner(object):
)
rc = process.wait()
if rc != 0:
self.log.error("ERROR (rc={0}) while execution {1}".format(rc,' '.join(cmd)))
self.log.error("ERROR (rc={0}) while execution {1}".format(rc, ' '.join(cmd)))
self.log.debug("_cleanup_ports: end.")
#todo: kill processes in given namespaces
return True
def _reschedule_agent_dhcp(self, agent_type):
@ -383,9 +435,6 @@ class NeutronCleaner(object):
self.log.debug("_reschedule_agents: end.")
def do(self, agent):
if self.options.get('list-agents'):
self._list_agents(agent)
return 0
if self.options.get('cleanup-ports'):
self._cleanup_ports(agent)
if self.options.get('reschedule'):
@ -415,6 +464,10 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Neutron network node cleaning tool.')
parser.add_argument("-c", "--auth-config", dest="authconf", default="/root/openrc",
help="Authenticating config FILE", metavar="FILE")
parser.add_argument("-t", "--auth-token", dest="auth-token", default=None,
help="Authenticating token (instead username/passwd)", metavar="TOKEN")
parser.add_argument("-u", "--admin-auth-url", dest="admin-auth-url", default=None,
help="Authenticating URL (admin)", metavar="URL")
parser.add_argument("--retries", dest="retries", type=int, default=50,
help="try NN retries for API call", metavar="NN")
parser.add_argument("--sleep", dest="sleep", type=int, default=2,
@ -425,18 +478,12 @@ if __name__ == '__main__':
help="cleanup ports for given agents on this node")
parser.add_argument("--activeonly", dest="activeonly", action="store_true", default=False,
help="cleanup only active ports")
# parser.add_argument("--cleanup-ns", dest="cleanup-ns", action="store_true", default=False,
# help="cleanup namespaces for given agents")
# parser.add_argument("--remove-agent", dest="remove-agent", action="store_true", default=False,
# help="cleanup namespaces for given agents")
parser.add_argument("--reschedule", dest="reschedule", action="store_true", default=False,
help="reschedule given agents")
parser.add_argument("--remove-dead", dest="remove-dead", action="store_true", default=False,
help="remove dead agents while rescheduling")
parser.add_argument("--test-alive-for-hostname", dest="test-hostnames", action="append",
help="testing agent's healthy for given hostname")
# parser.add_argument("--list", dest="list-agents", action="store_true", default=False,
# help="list agents and some additional information")
parser.add_argument("--external-bridge", dest="external-bridge", default="br-ex",
help="external bridge name", metavar="IFACE")
parser.add_argument("--integration-bridge", dest="integration-bridge", default="br-int",