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