components/openstack/heat/files/heat-upgrade
branchs11u2-sru
changeset 4156 4b1def16fe9b
child 4049 150852e281c4
child 4207 787ed839f409
equal deleted inserted replaced
4146:097063f324c0 4156:4b1def16fe9b
       
     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 HEAT_CONF_MAPPINGS = {
       
    34     # Deprecated group/name
       
    35     ('DEFAULT', 'stack_user_domain'): ('DEFAULT', 'stack_user_domain_id'),
       
    36     ('DEFAULT', 'rabbit_durable_queues'): ('DEFAULT', 'amqp_durable_queues'),
       
    37     ('rpc_notifier2', 'topics'): ('DEFAULT', 'notification_topics'),
       
    38     ('DEFAULT', 'log_config'): ('DEFAULT', 'log_config_append'),
       
    39     ('DEFAULT', 'logfile'): ('DEFAULT', 'log_file'),
       
    40     ('DEFAULT', 'logdir'): ('DEFAULT', 'log_dir'),
       
    41     ('DEFAULT', 'db_backend'): ('database', 'backend'),
       
    42     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
       
    43     ('DATABASE', 'sql_connection'): ('database', 'connection'),
       
    44     ('sql', 'connection'): ('database', 'connection'),
       
    45     ('DEFAULT', 'sql_idle_timeout'): ('database', 'idle_timeout'),
       
    46     ('DATABASE', 'sql_idle_timeout'): ('database', 'idle_timeout'),
       
    47     ('sql', 'idle_timeout'): ('database', 'idle_timeout'),
       
    48     ('DEFAULT', 'sql_min_pool_size'): ('database', 'min_pool_size'),
       
    49     ('DATABASE', 'sql_min_pool_size'): ('database', 'min_pool_size'),
       
    50     ('DEFAULT', 'sql_max_pool_size'): ('database', 'max_pool_size'),
       
    51     ('DATABASE', 'sql_max_pool_size'): ('database', 'max_pool_size'),
       
    52     ('DEFAULT', 'sql_max_retries'): ('database', 'max_retries'),
       
    53     ('DATABASE', 'sql_max_retries'): ('database', 'max_retries'),
       
    54     ('DEFAULT', 'sql_retry_interval'): ('database', 'retry_interval'),
       
    55     ('DATABASE', 'reconnect_interval'): ('database', 'retry_interval'),
       
    56     ('DEFAULT', 'sql_max_overflow'): ('database', 'max_overflow'),
       
    57     ('DATABASE', 'sqlalchemy_max_overflow'): ('database', 'max_overflow'),
       
    58     ('DEFAULT', 'sql_connection_debug'): ('database', 'connection_debug'),
       
    59     ('DEFAULT', 'sql_connection_trace'): ('database', 'connection_trace'),
       
    60     ('DATABASE', 'sqlalchemy_pool_timeout'): ('database', 'pool_timeout'),
       
    61     ('DEFAULT', 'memcache_servers'):
       
    62         ('keystone_authtoken', 'memcached_servers'),
       
    63     ('DEFAULT', 'matchmaker_ringfile'): ('matchmaker_ring', 'ringfile'),
       
    64     # No longer referenced by the service or causes a DeprecationWarning
       
    65     ('DEFAULT', 'instance_user'): (None, None),
       
    66     ('DEFAULT', 'onready'): (None, None),
       
    67     ('DEFAULT', 'list_notifier_drivers'): (None, None),
       
    68 }
       
    69 
       
    70 
       
    71 def update_mapping(section, key, mapping):
       
    72     """ look for deprecated variables and, if found, convert it to the new
       
    73     section/key.
       
    74     """
       
    75 
       
    76     if (section, key) in mapping:
       
    77         print "Deprecated value found: [%s] %s" % (section, key)
       
    78         section, key = mapping[(section, key)]
       
    79         if section is None and key is None:
       
    80             print "Removing from configuration"
       
    81         else:
       
    82             print "Updating to: [%s] %s" % (section, key)
       
    83     return section, key
       
    84 
       
    85 
       
    86 def alter_mysql_tables(engine):
       
    87     """ Convert MySQL tables to use utf8
       
    88     """
       
    89 
       
    90     import MySQLdb
       
    91 
       
    92     for _none in range(5):
       
    93         try:
       
    94             db = MySQLdb.connect(host=engine.url.host,
       
    95                                  user=engine.url.username,
       
    96                                  passwd=engine.url.password,
       
    97                                  db=engine.url.database)
       
    98             break
       
    99         except MySQLdb.OperationalError as err:
       
   100             # mysql is not ready. sleep for 2 more seconds
       
   101             time.sleep(2)
       
   102     else:
       
   103         print "Unable to connect to MySQL:  %s" % err
       
   104         print ("Please verify MySQL is properly configured and online "
       
   105                "before using svcadm(1M) to clear this service.")
       
   106         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)
       
   107 
       
   108     cursor = db.cursor()
       
   109     cursor.execute("ALTER DATABASE %s CHARACTER SET = 'utf8'" %
       
   110                    engine.url.database)
       
   111     cursor.execute("ALTER DATABASE %s COLLATE = 'utf8_general_ci'" %
       
   112                    engine.url.database)
       
   113     cursor.execute("SHOW tables")
       
   114     res = cursor.fetchall()
       
   115     if res:
       
   116         cursor.execute("SET foreign_key_checks = 0")
       
   117         for item in res:
       
   118             cursor.execute("ALTER TABLE %s.%s CONVERT TO "
       
   119                            "CHARACTER SET 'utf8', COLLATE 'utf8_general_ci'"
       
   120                            % (engine.url.database, item[0]))
       
   121         cursor.execute("SET foreign_key_checks = 1")
       
   122         db.commit()
       
   123         db.close()
       
   124 
       
   125 
       
   126 def modify_conf(old_file, mapping=None):
       
   127     """ Copy over all uncommented options from the old configuration file.  In
       
   128     addition, look for deprecated section/keys and convert them to the new
       
   129     section/key.
       
   130     """
       
   131 
       
   132     new_file = old_file + '.new'
       
   133 
       
   134     # open the previous version
       
   135     old = iniparse.ConfigParser()
       
   136     old.readfp(open(old_file))
       
   137 
       
   138     # open the new version
       
   139     new = iniparse.ConfigParser()
       
   140     try:
       
   141         new.readfp(open(new_file))
       
   142     except IOError as err:
       
   143         if err.errno == errno.ENOENT:
       
   144             # The upgrade did not deliver a .new file so, return
       
   145             print "%s not found - continuing with %s" % (new_file, old_file)
       
   146             return
       
   147         else:
       
   148             raise
       
   149     print "\nupdating %s" % old_file
       
   150 
       
   151     # walk every single section for uncommented options
       
   152     default_items = set(old.items('DEFAULT'))
       
   153     for section in old.sections() + ['DEFAULT']:
       
   154 
       
   155         # DEFAULT items show up in every section so remove them
       
   156         if section != 'DEFAULT':
       
   157             section_items = set(old.items(section)) - default_items
       
   158         else:
       
   159             section_items = default_items
       
   160 
       
   161         for key, value in section_items:
       
   162             # keep a copy of the old value
       
   163             oldvalue = value
       
   164 
       
   165             if mapping is not None:
       
   166                 section, key = update_mapping(section, key, mapping)
       
   167 
       
   168                 if section is None and key is None:
       
   169                     # option is deprecated so continue
       
   170                     continue
       
   171 
       
   172             if not new.has_section(section):
       
   173                 if section != 'DEFAULT':
       
   174                     new.add_section(section)
       
   175 
       
   176             # print to the log when a value for the same section.key is
       
   177             # changing to a new value
       
   178             try:
       
   179                 new_value = new.get(section, key)
       
   180                 if new_value != value and '%SERVICE' not in new_value:
       
   181                     print "Changing [%s] %s:\n- %s\n+ %s" % \
       
   182                         (section, key, oldvalue, new_value)
       
   183                     print
       
   184             except NoOptionError:
       
   185                 # the new configuration file does not have this option set so
       
   186                 # just continue
       
   187                 pass
       
   188 
       
   189             # Only copy the old value to the new conf file if the entry doesn't
       
   190             # exist or if it contains '%SERVICE'
       
   191             if not new.has_option(section, key) or \
       
   192                '%SERVICE' in new.get(section, key):
       
   193                 new.set(section, key, value)
       
   194 
       
   195     # copy the old conf file to a backup
       
   196     today = datetime.now().strftime("%Y%m%d%H%M%S")
       
   197     shutil.copy2(old_file, old_file + '.' + today)
       
   198 
       
   199     # copy the new conf file in place
       
   200     with open(old_file, 'wb+') as fh:
       
   201         new.write(fh)
       
   202 
       
   203 
       
   204 def start():
       
   205     # pull out the current version of config/upgrade-id
       
   206     p = Popen(['/usr/bin/svcprop', '-p', 'config/upgrade-id',
       
   207                os.environ['SMF_FMRI']], stdout=PIPE, stderr=PIPE)
       
   208     curr_ver, _err = p.communicate()
       
   209     curr_ver = curr_ver.strip()
       
   210 
       
   211     # extract the openstack-upgrade-id from the pkg
       
   212     p = Popen(['/usr/bin/pkg', 'contents', '-H', '-t', 'set', '-o', 'value',
       
   213                '-a', 'name=openstack.upgrade-id',
       
   214                'pkg:/cloud/openstack/heat'], stdout=PIPE, stderr=PIPE)
       
   215     pkg_ver, _err = p.communicate()
       
   216     pkg_ver = pkg_ver.strip()
       
   217 
       
   218     if curr_ver == pkg_ver:
       
   219         # No need to upgrade
       
   220         sys.exit(smf_include.SMF_EXIT_OK)
       
   221 
       
   222     # look for any .new files
       
   223     if glob.glob('/etc/heat/*.new'):
       
   224         # the versions are different, so perform an upgrade
       
   225         # modify the configuration files
       
   226         modify_conf('/etc/heat/api-paste.ini')
       
   227         modify_conf('/etc/heat/heat.conf', HEAT_CONF_MAPPINGS)
       
   228 
       
   229     config = iniparse.RawConfigParser()
       
   230     config.read('/etc/heat/heat.conf')
       
   231     # In certain cases the database section does not exist and the
       
   232     # default database chosen is sqlite.
       
   233     if config.has_section('database'):
       
   234         db_connection = config.get('database', 'connection')
       
   235 
       
   236         if db_connection.startswith('mysql'):
       
   237             engine = sqlalchemy.create_engine(db_connection)
       
   238             if engine.url.username != '%SERVICE_USER%':
       
   239                 alter_mysql_tables(engine)
       
   240                 print "altered character set to utf8 in heat tables"
       
   241 
       
   242     # update the current version
       
   243     check_call(['/usr/sbin/svccfg', '-s', os.environ['SMF_FMRI'], 'setprop',
       
   244                'config/upgrade-id', '=', pkg_ver])
       
   245     check_call(['/usr/sbin/svccfg', '-s', os.environ['SMF_FMRI'], 'refresh'])
       
   246 
       
   247     sys.exit(smf_include.SMF_EXIT_OK)
       
   248 
       
   249 
       
   250 if __name__ == '__main__':
       
   251     os.putenv('LC_ALL', 'C')
       
   252     try:
       
   253         smf_include.smf_main()
       
   254     except Exception as err:
       
   255         print 'Unknown error:  %s' % err
       
   256         print
       
   257         traceback.print_exc(file=sys.stdout)
       
   258         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)