components/openstack/nova/files/nova-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 NOVA_CONF_MAPPINGS = {
       
    34     # Deprecated group/name
       
    35     ('DEFAULT', 'rabbit_durable_queues'): ('DEFAULT', 'amqp_durable_queues'),
       
    36     ('rpc_notifier2', 'topics'): ('DEFAULT', 'notification_topics'),
       
    37     ('DEFAULT', 'quota_injected_file_path_bytes'):
       
    38         ('DEFAULT', 'quota_injected_file_path_length'),
       
    39     ('DEFAULT', 'log_config'): ('DEFAULT', 'log_config_append'),
       
    40     ('DEFAULT', 'logfile'): ('DEFAULT', 'log_file'),
       
    41     ('DEFAULT', 'logdir'): ('DEFAULT', 'log_dir'),
       
    42     ('DEFAULT', 'cinder_catalog_info'): ('cinder', 'catalog_info'),
       
    43     ('DEFAULT', 'cinder_endpoint_template'): ('cinder', 'endpoint_template'),
       
    44     ('DEFAULT', 'os_region_name'): ('cinder', 'os_region_name'),
       
    45     ('DEFAULT', 'cinder_ca_certificates_file'):
       
    46         ('cinder', 'ca_certificates_file'),
       
    47     ('DEFAULT', 'cinder_http_retries'): ('cinder', 'http_retries'),
       
    48     ('DEFAULT', 'cinder_http_timeout'): ('cinder', 'http_timeout'),
       
    49     ('DEFAULT', 'cinder_api_insecure'): ('cinder', 'api_insecure'),
       
    50     ('DEFAULT', 'cinder_cross_az_attach'): ('cinder', 'cross_az_attach'),
       
    51     ('DEFAULT', 'db_backend'): ('database', 'backend'),
       
    52     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
       
    53     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
       
    54     ('sql', 'connection'): ('database', 'connection'),
       
    55     ('DEFAULT', 'sql_idle_timeout'): ('database', 'idle_timeout'),
       
    56     ('DATABASE', 'sql_idle_timeout'): ('database', 'idle_timeout'),
       
    57     ('sql', 'idle_timeout'): ('database', 'idle_timeout'),
       
    58     ('DEFAULT', 'sql_min_pool_size'): ('database', 'min_pool_size'),
       
    59     ('DATABASE', 'sql_min_pool_size'): ('database', 'min_pool_size'),
       
    60     ('DEFAULT', 'sql_max_pool_size'): ('database', 'max_pool_size'),
       
    61     ('DATABASE', 'sql_max_pool_size'): ('database', 'max_pool_size'),
       
    62     ('DEFAULT', 'sql_max_retries'): ('database', 'max_retries'),
       
    63     ('DATABASE', 'sql_max_retries'): ('database', 'max_retries'),
       
    64     ('DEFAULT', 'sql_retry_interval'): ('database', 'retry_interval'),
       
    65     ('DATABASE', 'reconnect_interval'): ('database', 'retry_interval'),
       
    66     ('DEFAULT', 'sql_max_overflow'): ('database', 'max_overflow'),
       
    67     ('DATABASE', 'sqlalchemy_max_overflow'): ('database', 'max_overflow'),
       
    68     ('DEFAULT', 'sql_connection_debug'): ('database', 'connection_debug'),
       
    69     ('DEFAULT', 'sql_connection_trace'): ('database', 'connection_trace'),
       
    70     ('DATABASE', 'sqlalchemy_pool_timeout'): ('database', 'pool_timeout'),
       
    71     ('DEFAULT', 'glance_host'): ('glance', 'host'),
       
    72     ('DEFAULT', 'glance_port'): ('glance', 'port'),
       
    73     ('DEFAULT', 'glance_protocol'): ('glance', 'protocol'),
       
    74     ('DEFAULT', 'glance_api_servers'): ('glance', 'api_servers'),
       
    75     ('DEFAULT', 'glance_api_insecure'): ('glance', 'api_insecure'),
       
    76     ('DEFAULT', 'glance_num_retries'): ('glance', 'num_retries'),
       
    77     ('DEFAULT', 'memcache_servers'):
       
    78         ('keystone_authtoken', 'memcached_servers'),
       
    79     ('DEFAULT', 'matchmaker_ringfile'): ('matchmaker_ring', 'ringfile'),
       
    80     ('DEFAULT', 'service_neutron_metadata_proxy'):
       
    81         ('neutron', 'service_metadata_proxy'),
       
    82     ('DEFAULT', 'neutron_metadata_proxy_shared_secret'):
       
    83         ('neutron', 'metadata_proxy_shared_secret'),
       
    84     ('DEFAULT', 'neutron_url'): ('neutron', 'url'),
       
    85     ('DEFAULT', 'neutron_url_timeout'): ('neutron', 'url_timeout'),
       
    86     ('DEFAULT', 'neutron_admin_username'): ('neutron', 'admin_username'),
       
    87     ('DEFAULT', 'neutron_admin_password'): ('neutron', 'admin_password'),
       
    88     ('DEFAULT', 'neutron_admin_tenant_id'): ('neutron', 'admin_tenant_id'),
       
    89     ('DEFAULT', 'neutron_admin_tenant_name'): ('neutron', 'admin_tenant_name'),
       
    90     ('DEFAULT', 'neutron_region_name'): ('neutron', 'region_name'),
       
    91     ('DEFAULT', 'neutron_admin_auth_url'): ('neutron', 'admin_auth_url'),
       
    92     ('DEFAULT', 'neutron_api_insecure'): ('neutron', 'api_insecure'),
       
    93     ('DEFAULT', 'neutron_auth_strategy'): ('neutron', 'auth_strategy'),
       
    94     ('DEFAULT', 'neutron_ovs_bridge'): ('neutron', 'ovs_bridge'),
       
    95     ('DEFAULT', 'neutron_extension_sync_interval'):
       
    96         ('neutron', 'extension_sync_interval'),
       
    97     ('DEFAULT', 'neutron_ca_certificates_file'):
       
    98         ('neutron', 'ca_certificates_file'),
       
    99     ('DEFAULT', 'spicehtml5proxy_host'): ('spice', 'html5proxy_host'),
       
   100     ('DEFAULT', 'spicehtml5proxy_port'): ('spice', 'html5proxy_port'),
       
   101     # No longer referenced by the service
       
   102     ('DEFAULT', 'sql_connection'): (None, None),
       
   103 }
       
   104 
       
   105 
       
   106 def update_mapping(section, key, mapping):
       
   107     """ look for deprecated variables and, if found, convert it to the new
       
   108     section/key.
       
   109     """
       
   110 
       
   111     if (section, key) in mapping:
       
   112         print "Deprecated value found: [%s] %s" % (section, key)
       
   113         section, key = mapping[(section, key)]
       
   114         if section is None and key is None:
       
   115             print "Removing from configuration"
       
   116         else:
       
   117             print "Updating to: [%s] %s" % (section, key)
       
   118     return section, key
       
   119 
       
   120 
       
   121 def alter_mysql_tables(engine):
       
   122     """ Convert MySQL tables to use utf8
       
   123     """
       
   124 
       
   125     import MySQLdb
       
   126 
       
   127     for _none in range(5):
       
   128         try:
       
   129             db = MySQLdb.connect(host=engine.url.host,
       
   130                                  user=engine.url.username,
       
   131                                  passwd=engine.url.password,
       
   132                                  db=engine.url.database)
       
   133             break
       
   134         except MySQLdb.OperationalError as err:
       
   135             # mysql is not ready. sleep for 2 more seconds
       
   136             time.sleep(2)
       
   137     else:
       
   138         print "Unable to connect to MySQL:  %s" % err
       
   139         print ("Please verify MySQL is properly configured and online "
       
   140                "before using svcadm(1M) to clear this service.")
       
   141         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)
       
   142 
       
   143     cursor = db.cursor()
       
   144     cursor.execute("ALTER DATABASE %s CHARACTER SET = 'utf8'" %
       
   145                    engine.url.database)
       
   146     cursor.execute("ALTER DATABASE %s COLLATE = 'utf8_general_ci'" %
       
   147                    engine.url.database)
       
   148     cursor.execute("SHOW tables")
       
   149     res = cursor.fetchall()
       
   150     if res:
       
   151         cursor.execute("SET foreign_key_checks = 0")
       
   152         for item in res:
       
   153             cursor.execute("ALTER TABLE %s.%s CONVERT TO "
       
   154                            "CHARACTER SET 'utf8', COLLATE 'utf8_general_ci'"
       
   155                            % (engine.url.database, item[0]))
       
   156         cursor.execute("SET foreign_key_checks = 1")
       
   157         db.commit()
       
   158         db.close()
       
   159 
       
   160 
       
   161 def modify_conf(old_file, mapping=None):
       
   162     """ Copy over all uncommented options from the old configuration file.  In
       
   163     addition, look for deprecated section/keys and convert them to the new
       
   164     section/key.
       
   165     """
       
   166 
       
   167     new_file = old_file + '.new'
       
   168 
       
   169     # open the previous version
       
   170     old = iniparse.ConfigParser()
       
   171     old.readfp(open(old_file))
       
   172 
       
   173     # open the new version
       
   174     new = iniparse.ConfigParser()
       
   175     try:
       
   176         new.readfp(open(new_file))
       
   177     except IOError as err:
       
   178         if err.errno == errno.ENOENT:
       
   179             # The upgrade did not deliver a .new file so, return
       
   180             print "%s not found - continuing with %s" % (new_file, old_file)
       
   181             return
       
   182         else:
       
   183             raise
       
   184     print "\nupdating %s" % old_file
       
   185 
       
   186     # It's possible that nova.conf has database.connection commented out (to
       
   187     # use the default value).  If it is, and none of other deprecated values
       
   188     # are set, manually set database.connection in the new conf file.
       
   189     if 'nova.conf' in old_file:
       
   190         options = [
       
   191             ('database', 'sql_connection'),
       
   192             ('sql', 'connection'),
       
   193             ('database', 'connection'),
       
   194             ('DEFAULT', 'sql_connection')
       
   195         ]
       
   196         test = lambda x: old.has_section(x[0]) and old.has_option(x[0], x[1])
       
   197         if not any(map(test, options)):
       
   198             if old.has_option('DEFAULT', 'state_path'):
       
   199                 state_path = old.get('DEFAULT', 'state_path')
       
   200             else:
       
   201                 state_path = '/var/lib/nova'
       
   202 
       
   203             if old.has_option('DEFAULT', 'sqlite_db'):
       
   204                 sqlite_db = old.get('DEFAULT', 'sqlite_db')
       
   205             else:
       
   206                 sqlite_db = 'nova.sqlite'
       
   207 
       
   208             new.set('database', 'connection',
       
   209                     'sqlite:///%s/%s' % (state_path, sqlite_db))
       
   210 
       
   211     # walk every single section for uncommented options
       
   212     default_items = set(old.items('DEFAULT'))
       
   213     for section in old.sections() + ['DEFAULT']:
       
   214 
       
   215         # DEFAULT items show up in every section so remove them
       
   216         if section != 'DEFAULT':
       
   217             section_items = set(old.items(section)) - default_items
       
   218         else:
       
   219             section_items = default_items
       
   220 
       
   221         for key, value in section_items:
       
   222             # keep a copy of the old value
       
   223             oldvalue = value
       
   224 
       
   225             if mapping is not None:
       
   226                 section, key = update_mapping(section, key, mapping)
       
   227 
       
   228                 if section is None and key is None:
       
   229                     # option is deprecated so continue
       
   230                     continue
       
   231 
       
   232             if not new.has_section(section):
       
   233                 if section != 'DEFAULT':
       
   234                     new.add_section(section)
       
   235 
       
   236             # print to the log when a value for the same section.key is
       
   237             # changing to a new value
       
   238             try:
       
   239                 new_value = new.get(section, key)
       
   240                 if new_value != value and '%SERVICE' not in new_value:
       
   241                     print "Changing [%s] %s:\n- %s\n+ %s" % \
       
   242                         (section, key, oldvalue, new_value)
       
   243                     print
       
   244             except NoOptionError:
       
   245                 # the new configuration file does not have this option set so
       
   246                 # just continue
       
   247                 pass
       
   248 
       
   249             # Only copy the old value to the new conf file if the entry doesn't
       
   250             # exist or if it contains '%SERVICE'
       
   251             if not new.has_option(section, key) or \
       
   252                '%SERVICE' in new.get(section, key):
       
   253                 new.set(section, key, value)
       
   254 
       
   255     # copy the old conf file to a backup
       
   256     today = datetime.now().strftime("%Y%m%d%H%M%S")
       
   257     shutil.copy2(old_file, old_file + '.' + today)
       
   258 
       
   259     # copy the new conf file in place
       
   260     with open(old_file, 'wb+') as fh:
       
   261         new.write(fh)
       
   262 
       
   263 
       
   264 def start():
       
   265     # pull out the current version of config/upgrade-id
       
   266     p = Popen(['/usr/bin/svcprop', '-p', 'config/upgrade-id',
       
   267                os.environ['SMF_FMRI']], stdout=PIPE, stderr=PIPE)
       
   268     curr_ver, _err = p.communicate()
       
   269     curr_ver = curr_ver.strip()
       
   270 
       
   271     # extract the openstack-upgrade-id from the pkg
       
   272     p = Popen(['/usr/bin/pkg', 'contents', '-H', '-t', 'set', '-o', 'value',
       
   273                '-a', 'name=openstack.upgrade-id',
       
   274                'pkg:/cloud/openstack/nova'], stdout=PIPE, stderr=PIPE)
       
   275     pkg_ver, _err = p.communicate()
       
   276     pkg_ver = pkg_ver.strip()
       
   277 
       
   278     if curr_ver == pkg_ver:
       
   279         # No need to upgrade
       
   280         sys.exit(smf_include.SMF_EXIT_OK)
       
   281 
       
   282     # look for any .new files
       
   283     if glob.glob('/etc/nova/*.new'):
       
   284         # the versions are different, so perform an upgrade
       
   285         # modify the configuration files
       
   286         modify_conf('/etc/nova/api-paste.ini')
       
   287         modify_conf('/etc/nova/logging.conf')
       
   288         modify_conf('/etc/nova/nova.conf', NOVA_CONF_MAPPINGS)
       
   289 
       
   290     config = iniparse.RawConfigParser()
       
   291     config.read('/etc/nova/nova.conf')
       
   292     # In certain cases the database section does not exist and the
       
   293     # default database chosen is sqlite.
       
   294     if config.has_section('database'):
       
   295         db_connection = config.get('database', 'connection')
       
   296 
       
   297         if db_connection.startswith('mysql'):
       
   298             engine = sqlalchemy.create_engine(db_connection)
       
   299             if engine.url.username != '%SERVICE_USER%':
       
   300                 alter_mysql_tables(engine)
       
   301                 print "altered character set to utf8 in nova tables"
       
   302 
       
   303     # update the current version
       
   304     check_call(['/usr/sbin/svccfg', '-s', os.environ['SMF_FMRI'], 'setprop',
       
   305                'config/upgrade-id', '=', pkg_ver])
       
   306     check_call(['/usr/sbin/svccfg', '-s', os.environ['SMF_FMRI'], 'refresh'])
       
   307 
       
   308     sys.exit(smf_include.SMF_EXIT_OK)
       
   309 
       
   310 
       
   311 if __name__ == '__main__':
       
   312     os.putenv('LC_ALL', 'C')
       
   313     try:
       
   314         smf_include.smf_main()
       
   315     except Exception as err:
       
   316         print 'Unknown error:  %s' % err
       
   317         print
       
   318         traceback.print_exc(file=sys.stdout)
       
   319         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)