components/openstack/nova/files/nova-upgrade
changeset 4287 aba3ed31b37a
parent 4181 3ac4ce913bec
child 5405 66fd59fecd68
equal deleted inserted replaced
4286:a7f757b12343 4287:aba3ed31b37a
    12 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    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
    13 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    14 #    License for the specific language governing permissions and limitations
    14 #    License for the specific language governing permissions and limitations
    15 #    under the License.
    15 #    under the License.
    16 
    16 
    17 from ConfigParser import NoOptionError
       
    18 from datetime import datetime
       
    19 import errno
       
    20 import glob
    17 import glob
    21 import os
    18 import os
    22 import shutil
       
    23 from subprocess import check_call, Popen, PIPE
    19 from subprocess import check_call, Popen, PIPE
    24 import sys
    20 import sys
    25 import time
       
    26 import traceback
    21 import traceback
    27 
    22 
    28 import iniparse
    23 import iniparse
    29 import smf_include
    24 import smf_include
    30 import sqlalchemy
    25 import sqlalchemy
       
    26 
       
    27 from openstack_common import alter_mysql_tables, create_backups, modify_conf, \
       
    28     move_conf
    31 
    29 
    32 
    30 
    33 NOVA_CONF_MAPPINGS = {
    31 NOVA_CONF_MAPPINGS = {
    34     # Deprecated group/name
    32     # Deprecated group/name
    35     ('DEFAULT', 'rabbit_durable_queues'): ('DEFAULT', 'amqp_durable_queues'),
    33     ('DEFAULT', 'rabbit_durable_queues'): ('DEFAULT', 'amqp_durable_queues'),
    47     ('DEFAULT', 'cinder_http_retries'): ('cinder', 'http_retries'),
    45     ('DEFAULT', 'cinder_http_retries'): ('cinder', 'http_retries'),
    48     ('DEFAULT', 'cinder_http_timeout'): ('cinder', 'http_timeout'),
    46     ('DEFAULT', 'cinder_http_timeout'): ('cinder', 'http_timeout'),
    49     ('DEFAULT', 'cinder_api_insecure'): ('cinder', 'api_insecure'),
    47     ('DEFAULT', 'cinder_api_insecure'): ('cinder', 'api_insecure'),
    50     ('DEFAULT', 'cinder_cross_az_attach'): ('cinder', 'cross_az_attach'),
    48     ('DEFAULT', 'cinder_cross_az_attach'): ('cinder', 'cross_az_attach'),
    51     ('DEFAULT', 'db_backend'): ('database', 'backend'),
    49     ('DEFAULT', 'db_backend'): ('database', 'backend'),
    52     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
       
    53     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
    50     ('DEFAULT', 'sql_connection'): ('database', 'connection'),
    54     ('sql', 'connection'): ('database', 'connection'),
    51     ('sql', 'connection'): ('database', 'connection'),
    55     ('DEFAULT', 'sql_idle_timeout'): ('database', 'idle_timeout'),
    52     ('DEFAULT', 'sql_idle_timeout'): ('database', 'idle_timeout'),
    56     ('DATABASE', 'sql_idle_timeout'): ('database', 'idle_timeout'),
    53     ('DATABASE', 'sql_idle_timeout'): ('database', 'idle_timeout'),
    57     ('sql', 'idle_timeout'): ('database', 'idle_timeout'),
    54     ('sql', 'idle_timeout'): ('database', 'idle_timeout'),
    96         ('neutron', 'extension_sync_interval'),
    93         ('neutron', 'extension_sync_interval'),
    97     ('DEFAULT', 'neutron_ca_certificates_file'):
    94     ('DEFAULT', 'neutron_ca_certificates_file'):
    98         ('neutron', 'ca_certificates_file'),
    95         ('neutron', 'ca_certificates_file'),
    99     ('DEFAULT', 'spicehtml5proxy_host'): ('spice', 'html5proxy_host'),
    96     ('DEFAULT', 'spicehtml5proxy_host'): ('spice', 'html5proxy_host'),
   100     ('DEFAULT', 'spicehtml5proxy_port'): ('spice', 'html5proxy_port'),
    97     ('DEFAULT', 'spicehtml5proxy_port'): ('spice', 'html5proxy_port'),
   101     # No longer referenced by the service
       
   102     ('DEFAULT', 'sql_connection'): (None, None),
       
   103 }
    98 }
   104 
    99 
   105 
   100 NOVA_CONF_EXCEPTIONS = [
   106 def update_mapping(section, key, mapping):
   101     ('DEFAULT', 'ec2_workers'),
   107     """ look for deprecated variables and, if found, convert it to the new
   102     ('DEFAULT', 'osapi_compute_workers'),
   108     section/key.
   103     ('DEFAULT', 'metadata_workers'),
   109     """
   104     ('DEFAULT', 'lock_path'),
   110 
   105     ('DEFAULT', 'novncproxy_base_url'),
   111     if (section, key) in mapping:
   106     ('conductor', 'workers'),
   112         print "Deprecated value found: [%s] %s" % (section, key)
   107     ('database', 'connection'),
   113         section, key = mapping[(section, key)]
   108     ('keystone_authtoken', 'auth_uri'),
   114         if section is None and key is None:
   109     ('keystone_authtoken', 'signing_dir'),
   115             print "Removing from configuration"
   110     ('keystone_authtoken', 'identity_uri'),
   116         else:
   111     ('keystone_authtoken', 'admin_user'),
   117             print "Updating to: [%s] %s" % (section, key)
   112     ('keystone_authtoken', 'admin_password'),
   118     return section, key
   113     ('keystone_authtoken', 'admin_tenant_name'),
   119 
   114     ('neutron', 'service_metadata_proxy'),
   120 
   115 ]
   121 def alter_mysql_tables(engine):
   116 
   122     """ Convert MySQL tables to use utf8
   117 NOVA_MOVE_CONFIG = {
   123     """
   118     ('filter:authtoken', 'auth_uri'): ('keystone_authtoken', 'auth_uri'),
   124 
   119     ('filter:authtoken', 'identity_uri'):
   125     import MySQLdb
   120         ('keystone_authtoken', 'identity_uri'),
   126 
   121     ('filter:authtoken', 'admin_tenant_name'):
   127     for _none in range(5):
   122         ('keystone_authtoken', 'admin_tenant_name'),
   128         try:
   123     ('filter:authtoken', 'admin_user'): ('keystone_authtoken', 'admin_user'),
   129             db = MySQLdb.connect(host=engine.url.host,
   124     ('filter:authtoken', 'admin_password'):
   130                                  user=engine.url.username,
   125         ('keystone_authtoken', 'admin_password'),
   131                                  passwd=engine.url.password,
   126     ('filter:authtoken', 'signing_dir'): ('keystone_authtoken', 'signing_dir'),
   132                                  db=engine.url.database)
   127     ('filter:authtoken', 'auth_version'):
   133             break
   128         ('keystone_authtoken', 'auth_version'),
   134         except MySQLdb.OperationalError as err:
   129 }
   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             oldsection = section
       
   225 
       
   226             if mapping is not None:
       
   227                 section, key = update_mapping(section, key, mapping)
       
   228 
       
   229                 if section is None and key is None:
       
   230                     # option is deprecated so continue
       
   231                     continue
       
   232 
       
   233             if not new.has_section(section):
       
   234                 if section != 'DEFAULT':
       
   235                     new.add_section(section)
       
   236 
       
   237             # print to the log when a value for the same section.key is
       
   238             # changing to a new value
       
   239             try:
       
   240                 new_value = new.get(section, key)
       
   241                 if new_value != value and '%SERVICE' not in new_value:
       
   242                     print "Changing [%s] %s:\n- %s\n+ %s" % \
       
   243                         (section, key, oldvalue, new_value)
       
   244                     print
       
   245             except NoOptionError:
       
   246                 # the new configuration file does not have this option set so
       
   247                 # just continue
       
   248                 pass
       
   249 
       
   250             # Only copy the old value to the new conf file if the entry doesn't
       
   251             # exist or if it contains '%SERVICE'
       
   252             if not new.has_option(section, key) or \
       
   253                '%SERVICE' in new.get(section, key):
       
   254                 new.set(section, key, value)
       
   255             section = oldsection
       
   256 
       
   257     # copy the old conf file to a backup
       
   258     today = datetime.now().strftime("%Y%m%d%H%M%S")
       
   259     shutil.copy2(old_file, old_file + '.' + today)
       
   260 
       
   261     # copy the new conf file in place
       
   262     with open(old_file, 'wb+') as fh:
       
   263         new.write(fh)
       
   264 
   130 
   265 
   131 
   266 def start():
   132 def start():
   267     # pull out the current version of config/upgrade-id
   133     # pull out the current version of config/upgrade-id
   268     p = Popen(['/usr/bin/svcprop', '-p', 'config/upgrade-id',
   134     p = Popen(['/usr/bin/svcprop', '-p', 'config/upgrade-id',
   283 
   149 
   284     # look for any .new files
   150     # look for any .new files
   285     if glob.glob('/etc/nova/*.new'):
   151     if glob.glob('/etc/nova/*.new'):
   286         # the versions are different, so perform an upgrade
   152         # the versions are different, so perform an upgrade
   287         # modify the configuration files
   153         # modify the configuration files
       
   154 
       
   155         # backup all the old configuration files
       
   156         create_backups('/etc/nova')
       
   157 
   288         modify_conf('/etc/nova/api-paste.ini')
   158         modify_conf('/etc/nova/api-paste.ini')
   289         modify_conf('/etc/nova/logging.conf')
   159         modify_conf('/etc/nova/logging.conf')
   290         modify_conf('/etc/nova/nova.conf', NOVA_CONF_MAPPINGS)
   160 
       
   161         # It's possible that nova.conf has database.connection commented out
       
   162         # (to use the default value).  If it is, and none of other deprecated
       
   163         # values are set, manually set database.connection in the new conf
       
   164         # file.
       
   165         if os.path.exists('/etc/nova/nova.conf.new'):
       
   166 
       
   167             # open the previous version
       
   168             old = iniparse.ConfigParser()
       
   169             old.readfp(open('/etc/nova/nova.conf'))
       
   170 
       
   171             # open the new version
       
   172             new = iniparse.ConfigParser()
       
   173             new.readfp(open('/etc/nova/nova.conf.new'))
       
   174 
       
   175             options = [
       
   176                 ('database', 'sql_connection'),
       
   177                 ('sql', 'connection'),
       
   178                 ('database', 'connection'),
       
   179                 ('DEFAULT', 'sql_connection')
       
   180             ]
       
   181             test = lambda x: old.has_section(x[0]) and \
       
   182                     old.has_option(x[0], x[1])
       
   183 
       
   184             if not any(map(test, options)):
       
   185                 if old.has_option('DEFAULT', 'state_path'):
       
   186                     state_path = old.get('DEFAULT', 'state_path')
       
   187                 else:
       
   188                     state_path = '/var/lib/nova'
       
   189 
       
   190                 if old.has_option('DEFAULT', 'sqlite_db'):
       
   191                     sqlite_db = old.get('DEFAULT', 'sqlite_db')
       
   192                 else:
       
   193                     sqlite_db = 'nova.sqlite'
       
   194 
       
   195                 new.set('database', 'connection',
       
   196                         'sqlite:///%s/%s' % (state_path, sqlite_db))
       
   197 
       
   198                 with open('/etc/nova/nova.conf.new', 'w+') as fh:
       
   199                     new.write(fh)
       
   200 
       
   201         # before modifying nova.conf, move the [filter:authtoken] entries from
       
   202         # the updated api-paste.ini to the old nova.conf
       
   203         move_conf('/etc/nova/api-paste.ini', '/etc/nova/nova.conf',
       
   204                   NOVA_MOVE_CONFIG)
       
   205 
       
   206         modify_conf('/etc/nova/nova.conf', NOVA_CONF_MAPPINGS,
       
   207                     NOVA_CONF_EXCEPTIONS)
   291 
   208 
   292     config = iniparse.RawConfigParser()
   209     config = iniparse.RawConfigParser()
   293     config.read('/etc/nova/nova.conf')
   210     config.read('/etc/nova/nova.conf')
   294     # In certain cases the database section does not exist and the
   211     # In certain cases the database section does not exist and the
   295     # default database chosen is sqlite.
   212     # default database chosen is sqlite.
   312 
   229 
   313 if __name__ == '__main__':
   230 if __name__ == '__main__':
   314     os.putenv('LC_ALL', 'C')
   231     os.putenv('LC_ALL', 'C')
   315     try:
   232     try:
   316         smf_include.smf_main()
   233         smf_include.smf_main()
       
   234     except RuntimeError:
       
   235         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)
   317     except Exception as err:
   236     except Exception as err:
   318         print 'Unknown error:  %s' % err
   237         print 'Unknown error:  %s' % err
   319         print
   238         print
   320         traceback.print_exc(file=sys.stdout)
   239         traceback.print_exc(file=sys.stdout)
   321         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)
   240         sys.exit(smf_include.SMF_EXIT_ERR_FATAL)