components/openstack/keystone/files/keystone-upgrade
changeset 3998 5bd484384122
child 4049 150852e281c4
child 4207 787ed839f409
equal deleted inserted replaced
3997:0ca3f3d6c919 3998:5bd484384122
       
     1 #!/usr/bin/python2.6
       
     2 
       
     3 # Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
       
     4 #
       
     5 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
       
     6 #    not use this file except in compliance with the License. You may obtain
       
     7 #    a copy of the License at
       
     8 #
       
     9 #         http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 #    Unless required by applicable law or agreed to in writing, software
       
    12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
       
    13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
       
    14 #    License for the specific language governing permissions and limitations
       
    15 #    under the License.
       
    16 
       
    17 from ConfigParser import NoOptionError
       
    18 from datetime import datetime
       
    19 import errno
       
    20 import glob
       
    21 import os
       
    22 import shutil
       
    23 from subprocess import check_call, Popen, PIPE
       
    24 import sys
       
    25 import time
       
    26 import traceback
       
    27 
       
    28 import iniparse
       
    29 import smf_include
       
    30 import sqlalchemy
       
    31 
       
    32 
       
    33 KEYSTONE_CONF_MAPPINGS = {
       
    34     # Deprecated group/name
       
    35     ('DEFAULT', 'rabbit_durable_queues'): ('DEFAULT', 'amqp_durable_queues'),
       
    36     ('rpc_notifier2', 'topics'): ('DEFAULT', 'notification_topics'),
       
    37     ('DEFAULT', 'log_config'): ('DEFAULT', 'log_config_append'),
       
    38     ('DEFAULT', 'logfile'): ('DEFAULT', 'log_file'),
       
    39     ('DEFAULT', 'logdir'): ('DEFAULT', 'log_dir'),
       
    40     ('DEFAULT', 'db_backend'): ('database', 'backend'),
       
    41     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
       
    42     ('DATABASE', 'sql_connection'): ('database', 'connection'),
       
    43     ('sql', 'connection'): ('database', 'connection'),
       
    44     ('DEFAULT', 'sql_idle_timeout'): ('database', 'idle_timeout'),
       
    45     ('DATABASE', 'sql_idle_timeout'): ('database', 'idle_timeout'),
       
    46     ('sql', 'idle_timeout'): ('database', 'idle_timeout'),
       
    47     ('DEFAULT', 'sql_min_pool_size'): ('database', 'min_pool_size'),
       
    48     ('DATABASE', 'sql_min_pool_size'): ('database', 'min_pool_size'),
       
    49     ('DEFAULT', 'sql_max_pool_size'): ('database', 'max_pool_size'),
       
    50     ('DATABASE', 'sql_max_pool_size'): ('database', 'max_pool_size'),
       
    51     ('DEFAULT', 'sql_max_retries'): ('database', 'max_retries'),
       
    52     ('DATABASE', 'sql_max_retries'): ('database', 'max_retries'),
       
    53     ('DEFAULT', 'sql_retry_interval'): ('database', 'retry_interval'),
       
    54     ('DATABASE', 'reconnect_interval'): ('database', 'retry_interval'),
       
    55     ('DEFAULT', 'sql_max_overflow'): ('database', 'max_overflow'),
       
    56     ('DATABASE', 'sqlalchemy_max_overflow'): ('database', 'max_overflow'),
       
    57     ('DEFAULT', 'sql_connection_debug'): ('database', 'connection_debug'),
       
    58     ('DEFAULT', 'sql_connection_trace'): ('database', 'connection_trace'),
       
    59     ('DATABASE', 'sqlalchemy_pool_timeout'): ('database', 'pool_timeout'),
       
    60     ('ldap', 'tenant_tree_dn'): ('ldap', 'project_tree_dn'),
       
    61     ('ldap', 'tenant_filter'): ('ldap', 'project_filter'),
       
    62     ('ldap', 'tenant_objectclass'): ('ldap', 'project_objectclass'),
       
    63     ('ldap', 'tenant_id_attribute'): ('ldap', 'project_id_attribute'),
       
    64     ('ldap', 'tenant_member_attribute'): ('ldap', 'project_member_attribute'),
       
    65     ('ldap', 'tenant_name_attribute'): ('ldap', 'project_name_attribute'),
       
    66     ('ldap', 'tenant_desc_attribute'): ('ldap', 'project_desc_attribute'),
       
    67     ('ldap', 'tenant_enabled_attribute'):
       
    68         ('ldap', 'project_enabled_attribute'),
       
    69     ('ldap', 'tenant_domain_id_attribute'):
       
    70         ('ldap', 'project_domain_id_attribute'),
       
    71     ('ldap', 'tenant_attribute_ignore'): ('ldap', 'project_attribute_ignore'),
       
    72     ('ldap', 'tenant_allow_create'): ('ldap', 'project_allow_create'),
       
    73     ('ldap', 'tenant_allow_update'): ('ldap', 'project_allow_update'),
       
    74     ('ldap', 'tenant_allow_delete'): ('ldap', 'project_allow_delete'),
       
    75     ('ldap', 'tenant_enabled_emulation'):
       
    76         ('ldap', 'project_enabled_emulation'),
       
    77     ('ldap', 'tenant_enabled_emulation_dn'):
       
    78         ('ldap', 'project_enabled_emulation_dn'),
       
    79     ('ldap', 'tenant_additional_attribute_mapping'):
       
    80         ('ldap', 'project_additional_attribute_mapping'),
       
    81     ('DEFAULT', 'matchmaker_ringfile'): ('matchmaker_ring', 'ringfile'),
       
    82 }
       
    83 
       
    84 
       
    85 def update_mapping(section, key, mapping):
       
    86     """ look for deprecated variables and, if found, convert it to the new
       
    87     section/key.
       
    88     """
       
    89 
       
    90     if (section, key) in mapping:
       
    91         print "Deprecated value found: [%s] %s" % (section, key)
       
    92         section, key = mapping[(section, key)]
       
    93         if section is None and key is None:
       
    94             print "Removing from configuration"
       
    95         else:
       
    96             print "Updating to: [%s] %s" % (section, key)
       
    97     return section, key
       
    98 
       
    99 
       
   100 def alter_mysql_tables(engine):
       
   101     """ Convert MySQL tables to use utf8
       
   102     """
       
   103 
       
   104     import MySQLdb
       
   105 
       
   106     for _none in range(5):
       
   107         try:
       
   108             db = MySQLdb.connect(host=engine.url.host,
       
   109                                  user=engine.url.username,
       
   110                                  passwd=engine.url.password,
       
   111                                  db=engine.url.database)
       
   112             break
       
   113         except MySQLdb.OperationalError as err:
       
   114             # mysql is not ready. sleep for 2 more seconds
       
   115             time.sleep(2)
       
   116     else:
       
   117         print "Unable to connect to MySQL:  %s" % err
       
   118         print ("Please verify MySQL is properly configured and online "
       
   119                "before using svcadm(1M) to clear this service.")
       
   120         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)
       
   121 
       
   122     cursor = db.cursor()
       
   123     cursor.execute("ALTER DATABASE %s CHARACTER SET = 'utf8'" %
       
   124                    engine.url.database)
       
   125     cursor.execute("ALTER DATABASE %s COLLATE = 'utf8_general_ci'" %
       
   126                    engine.url.database)
       
   127     cursor.execute("SHOW tables")
       
   128     res = cursor.fetchall()
       
   129     if res:
       
   130         cursor.execute("SET foreign_key_checks = 0")
       
   131         for item in res:
       
   132             cursor.execute("ALTER TABLE %s.%s CONVERT TO "
       
   133                            "CHARACTER SET 'utf8', COLLATE 'utf8_general_ci'"
       
   134                            % (engine.url.database, item[0]))
       
   135         cursor.execute("SET foreign_key_checks = 1")
       
   136         db.commit()
       
   137         db.close()
       
   138 
       
   139 
       
   140 def modify_conf(old_file, mapping=None):
       
   141     """ Copy over all uncommented options from the old configuration file.  In
       
   142     addition, look for deprecated section/keys and convert them to the new
       
   143     section/key.
       
   144     """
       
   145 
       
   146     new_file = old_file + '.new'
       
   147 
       
   148     # open the previous version
       
   149     old = iniparse.ConfigParser()
       
   150     old.readfp(open(old_file))
       
   151 
       
   152     # open the new version
       
   153     new = iniparse.ConfigParser()
       
   154     try:
       
   155         new.readfp(open(new_file))
       
   156     except IOError as err:
       
   157         if err.errno == errno.ENOENT:
       
   158             # The upgrade did not deliver a .new file so, return
       
   159             print "%s not found - continuing with %s" % (new_file, old_file)
       
   160             return
       
   161         else:
       
   162             raise
       
   163     print "\nupdating %s" % old_file
       
   164 
       
   165     # walk every single section for uncommented options
       
   166     default_items = set(old.items('DEFAULT'))
       
   167     for section in old.sections() + ['DEFAULT']:
       
   168 
       
   169         # DEFAULT items show up in every section so remove them
       
   170         if section != 'DEFAULT':
       
   171             section_items = set(old.items(section)) - default_items
       
   172         else:
       
   173             section_items = default_items
       
   174 
       
   175         for key, value in section_items:
       
   176             # keep a copy of the old value
       
   177             oldvalue = value
       
   178 
       
   179             if mapping is not None:
       
   180                 section, key = update_mapping(section, key, mapping)
       
   181 
       
   182                 if section is None and key is None:
       
   183                     # option is deprecated so continue
       
   184                     continue
       
   185 
       
   186             if not new.has_section(section):
       
   187                 if section != 'DEFAULT':
       
   188                     new.add_section(section)
       
   189 
       
   190             # print to the log when a value for the same section.key is
       
   191             # changing to a new value
       
   192             try:
       
   193                 new_value = new.get(section, key)
       
   194                 if new_value != value and '%SERVICE' not in new_value:
       
   195                     print "Changing [%s] %s:\n- %s\n+ %s" % \
       
   196                         (section, key, oldvalue, new_value)
       
   197                     print
       
   198             except NoOptionError:
       
   199                 # the new configuration file does not have this option set so
       
   200                 # just continue
       
   201                 pass
       
   202 
       
   203             # Only copy the old value to the new conf file if the entry doesn't
       
   204             # exist or if it contains '%SERVICE'
       
   205             if not new.has_option(section, key) or \
       
   206                '%SERVICE' in new.get(section, key):
       
   207                 new.set(section, key, value)
       
   208 
       
   209     # copy the old conf file to a backup
       
   210     today = datetime.now().strftime("%Y%m%d%H%M%S")
       
   211     shutil.copy2(old_file, old_file + '.' + today)
       
   212 
       
   213     # copy the new conf file in place
       
   214     with open(old_file, 'wb+') as fh:
       
   215         new.write(fh)
       
   216 
       
   217 
       
   218 def start():
       
   219     # pull out the current version of config/upgrade-id
       
   220     p = Popen(['/usr/bin/svcprop', '-p', 'config/upgrade-id',
       
   221                os.environ['SMF_FMRI']], stdout=PIPE, stderr=PIPE)
       
   222     curr_ver, _err = p.communicate()
       
   223     curr_ver = curr_ver.strip()
       
   224 
       
   225     # extract the openstack-upgrade-id from the pkg
       
   226     p = Popen(['/usr/bin/pkg', 'contents', '-H', '-t', 'set', '-o', 'value',
       
   227                '-a', 'name=openstack.upgrade-id',
       
   228                'pkg:/cloud/openstack/keystone'], stdout=PIPE, stderr=PIPE)
       
   229     pkg_ver, _err = p.communicate()
       
   230     pkg_ver = pkg_ver.strip()
       
   231 
       
   232     if curr_ver == pkg_ver:
       
   233         # No need to upgrade
       
   234         sys.exit(smf_include.SMF_EXIT_OK)
       
   235 
       
   236     # look for any .new files
       
   237     if glob.glob('/etc/keystone/*.new'):
       
   238         # the versions are different, so perform an upgrade
       
   239         # modify the configuration files
       
   240         modify_conf('/etc/keystone/keystone.conf', KEYSTONE_CONF_MAPPINGS)
       
   241         modify_conf('/etc/keystone/keystone-paste.ini')
       
   242         modify_conf('/etc/keystone/logging.conf')
       
   243 
       
   244     config = iniparse.RawConfigParser()
       
   245     config.read('/etc/keystone/keystone.conf')
       
   246     # In certain cases the database section does not exist and the
       
   247     # default database chosen is sqlite.
       
   248     if config.has_section('database'):
       
   249         db_connection = config.get('database', 'connection')
       
   250 
       
   251         if db_connection.startswith('mysql'):
       
   252             engine = sqlalchemy.create_engine(db_connection)
       
   253             if engine.url.username != '%SERVICE_USER%':
       
   254                 alter_mysql_tables(engine)
       
   255                 print "altered character set to utf8 in keystone tables"
       
   256 
       
   257     # update the current version
       
   258     check_call(['/usr/sbin/svccfg', '-s', os.environ['SMF_FMRI'], 'setprop',
       
   259                'config/upgrade-id', '=', pkg_ver])
       
   260     check_call(['/usr/sbin/svccfg', '-s', os.environ['SMF_FMRI'], 'refresh'])
       
   261 
       
   262     sys.exit(smf_include.SMF_EXIT_OK)
       
   263 
       
   264 
       
   265 if __name__ == '__main__':
       
   266     os.putenv('LC_ALL', 'C')
       
   267     try:
       
   268         smf_include.smf_main()
       
   269     except Exception as err:
       
   270         print 'Unknown error:  %s' % err
       
   271         print
       
   272         traceback.print_exc(file=sys.stdout)
       
   273         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)