Coverage for nova/cmd/status.py: 100%

138 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2016 IBM Corp. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); you may 

4# not use this file except in compliance with the License. You may obtain 

5# a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

12# License for the specific language governing permissions and limitations 

13# under the License. 

14 

15""" 

16CLI interface for nova status commands. 

17""" 

18 

19import functools 

20import sys 

21import traceback 

22 

23from keystoneauth1 import exceptions as ks_exc 

24import microversion_parse 

25from oslo_config import cfg 

26from oslo_upgradecheck import common_checks 

27from oslo_upgradecheck import upgradecheck 

28import sqlalchemy as sa 

29from sqlalchemy import func as sqlfunc 

30 

31from nova.cmd import common as cmd_common 

32import nova.conf 

33from nova import config 

34from nova import context as nova_context 

35from nova.db.api import api as api_db_api 

36from nova.db.main import api as main_db_api 

37from nova import exception 

38from nova.i18n import _ 

39from nova.objects import cell_mapping as cell_mapping_obj 

40# NOTE(lyarwood): The following are imported as machine_type_utils expects them 

41# to be registered under nova.objects when called via _check_machine_type_set 

42from nova.objects import image_meta as image_meta_obj # noqa: F401 

43from nova.objects import instance as instance_obj # noqa: F401 

44from nova import utils 

45from nova import version 

46from nova.virt.libvirt import machine_type_utils 

47from nova.volume import cinder 

48 

49CONF = nova.conf.CONF 

50 

51# NOTE(gibi): 1.36 is required by nova-scheduler to support the same_subtree 

52# queryparam to make GET /allocation_candidates require that a list of request 

53# groups are satisfied from the same provider subtree. 

54# NOTE: If you bump this version, remember to update the history 

55# section in the nova-status man page (doc/source/cli/nova-status). 

56MIN_PLACEMENT_MICROVERSION = "1.36" 

57 

58# NOTE(mriedem): 3.44 is needed to work with volume attachment records which 

59# are required for supporting multi-attach capable volumes. 

60MIN_CINDER_MICROVERSION = '3.44' 

61 

62 

63class UpgradeCommands(upgradecheck.UpgradeCommands): 

64 """Commands related to upgrades. 

65 

66 The subcommands here must not rely on the nova object model since they 

67 should be able to run on n-1 data. Any queries to the database should be 

68 done through the sqlalchemy query language directly like the database 

69 schema migrations. 

70 """ 

71 

72 def _count_compute_nodes(self, context=None): 

73 """Returns the number of compute nodes in the cell database.""" 

74 # NOTE(mriedem): This does not filter based on the service status 

75 # because a disabled nova-compute service could still be reporting 

76 # inventory info to the placement service. There could be an outside 

77 # chance that there are compute node records in the database for 

78 # disabled nova-compute services that aren't yet upgraded to Ocata or 

79 # the nova-compute service was deleted and the service isn't actually 

80 # running on the compute host but the operator hasn't cleaned up the 

81 # compute_nodes entry in the database yet. We consider those edge cases 

82 # here and the worst case scenario is we give a warning that there are 

83 # more compute nodes than resource providers. We can tighten this up 

84 # later if needed, for example by not including compute nodes that 

85 # don't have a corresponding nova-compute service in the services 

86 # table, or by only counting compute nodes with a service version of at 

87 # least 15 which was the highest service version when Newton was 

88 # released. 

89 meta = sa.MetaData() 

90 engine = main_db_api.get_engine(context=context) 

91 compute_nodes = sa.Table('compute_nodes', meta, autoload_with=engine) 

92 with engine.connect() as conn: 

93 return conn.execute( 

94 sa.select(sqlfunc.count()).select_from(compute_nodes).where( 

95 compute_nodes.c.deleted == 0 

96 ) 

97 ).scalars().first() 

98 

99 def _check_cellsv2(self): 

100 """Checks to see if cells v2 has been setup. 

101 

102 These are the same checks performed in the 030_require_cell_setup API 

103 DB migration except we expect this to be run AFTER the 

104 nova-manage cell_v2 simple_cell_setup command, which would create the 

105 cell and host mappings and sync the cell0 database schema, so we don't 

106 check for flavors at all because you could create those after doing 

107 this on an initial install. This also has to be careful about checking 

108 for compute nodes if there are no host mappings on a fresh install. 

109 """ 

110 meta = sa.MetaData() 

111 engine = api_db_api.get_engine() 

112 

113 cell_mappings = self._get_cell_mappings() 

114 count = len(cell_mappings) 

115 # Two mappings are required at a minimum, cell0 and your first cell 

116 if count < 2: 

117 msg = _('There needs to be at least two cell mappings, one for ' 

118 'cell0 and one for your first cell. Run command ' 

119 '\'nova-manage cell_v2 simple_cell_setup\' and then ' 

120 'retry.') 

121 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

122 

123 cell0 = any(mapping.is_cell0() for mapping in cell_mappings) 

124 if not cell0: 

125 msg = _('No cell0 mapping found. Run command ' 

126 '\'nova-manage cell_v2 simple_cell_setup\' and then ' 

127 'retry.') 

128 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

129 

130 host_mappings = sa.Table('host_mappings', meta, autoload_with=engine) 

131 

132 with engine.connect() as conn: 

133 count = conn.execute( 

134 sa.select(sqlfunc.count()).select_from(host_mappings) 

135 ).scalars().first() 

136 

137 if count == 0: 

138 # This may be a fresh install in which case there may not be any 

139 # compute_nodes in the cell database if the nova-compute service 

140 # hasn't started yet to create those records. So let's query the 

141 # cell database for compute_nodes records and if we find at least 

142 # one it's a failure. 

143 num_computes = self._count_compute_nodes() 

144 if num_computes > 0: 

145 msg = _('No host mappings found but there are compute nodes. ' 

146 'Run command \'nova-manage cell_v2 ' 

147 'simple_cell_setup\' and then retry.') 

148 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

149 

150 msg = _('No host mappings or compute nodes were found. Remember ' 

151 'to run command \'nova-manage cell_v2 discover_hosts\' ' 

152 'when new compute hosts are deployed.') 

153 return upgradecheck.Result(upgradecheck.Code.SUCCESS, msg) 

154 

155 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

156 

157 @staticmethod 

158 def _placement_get(path): 

159 """Do an HTTP get call against placement engine. 

160 

161 This is in a dedicated method to make it easier for unit 

162 testing purposes. 

163 

164 """ 

165 client = utils.get_ksa_adapter('placement') 

166 return client.get(path, raise_exc=True).json() 

167 

168 def _check_placement(self): 

169 """Checks to see if the placement API is ready for scheduling. 

170 

171 Checks to see that the placement API service is registered in the 

172 service catalog and that we can make requests against it. 

173 """ 

174 try: 

175 # TODO(efried): Use ksa's version filtering in _placement_get 

176 versions = self._placement_get("/") 

177 max_version = microversion_parse.parse_version_string( 

178 versions["versions"][0]["max_version"]) 

179 needs_version = microversion_parse.parse_version_string( 

180 MIN_PLACEMENT_MICROVERSION) 

181 if max_version < needs_version: 

182 msg = (_('Placement API version %(needed)s needed, ' 

183 'you have %(current)s.') % 

184 {'needed': needs_version, 'current': max_version}) 

185 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

186 except ks_exc.MissingAuthPlugin: 

187 msg = _('No credentials specified for placement API in nova.conf.') 

188 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

189 except ks_exc.Unauthorized: 

190 msg = _('Placement service credentials do not work.') 

191 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

192 except ks_exc.EndpointNotFound: 

193 msg = _('Placement API endpoint not found.') 

194 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

195 except ks_exc.DiscoveryFailure: 

196 msg = _('Discovery for placement API URI failed.') 

197 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

198 except ks_exc.NotFound: 

199 msg = _('Placement API does not seem to be running.') 

200 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

201 

202 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

203 

204 @staticmethod 

205 def _get_cell_mappings(): 

206 """Queries the API database for cell mappings. 

207 

208 .. note:: This method is unique in that it queries the database using 

209 CellMappingList.get_all() rather than a direct query using 

210 the sqlalchemy models. This is because 

211 CellMapping.database_connection can be a template and the 

212 object takes care of formatting the URL. We cannot use 

213 RowProxy objects from sqlalchemy because we cannot set the 

214 formatted 'database_connection' value back on those objects 

215 (they are read-only). 

216 

217 :returns: list of nova.objects.CellMapping objects 

218 """ 

219 ctxt = nova_context.get_admin_context() 

220 cell_mappings = cell_mapping_obj.CellMappingList.get_all(ctxt) 

221 return cell_mappings 

222 

223 def _check_cinder(self): 

224 """Checks to see that the cinder API is available at a given minimum 

225 microversion. 

226 """ 

227 # Check to see if nova is even configured for Cinder yet (fresh install 

228 # or maybe not using Cinder at all). 

229 if CONF.cinder.auth_type is None: 

230 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

231 

232 try: 

233 # TODO(mriedem): Eventually use get_ksa_adapter here when it 

234 # supports cinder. 

235 cinder.is_microversion_supported( 

236 nova_context.get_admin_context(), MIN_CINDER_MICROVERSION) 

237 except exception.CinderAPIVersionNotAvailable: 

238 return upgradecheck.Result( 

239 upgradecheck.Code.FAILURE, 

240 _('Cinder API %s or greater is required. Deploy at least ' 

241 'Cinder 12.0.0 (Queens).') % MIN_CINDER_MICROVERSION) 

242 except Exception as ex: 

243 # Anything else trying to connect, like bad config, is out of our 

244 # hands so just return a warning. 

245 return upgradecheck.Result( 

246 upgradecheck.Code.WARNING, 

247 _('Unable to determine Cinder API version due to error: %s') % 

248 str(ex)) 

249 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

250 

251 def _check_old_computes(self): 

252 # warn if there are computes in the system older than the previous 

253 # major release 

254 try: 

255 utils.raise_if_old_compute() 

256 except exception.TooOldComputeService as e: 

257 return upgradecheck.Result(upgradecheck.Code.FAILURE, str(e)) 

258 

259 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

260 

261 def _check_machine_type_set(self): 

262 ctxt = nova_context.get_admin_context() 

263 if machine_type_utils.get_instances_without_type(ctxt): 

264 msg = (_(""" 

265Instances found without hw_machine_type set. This warning can be ignored if 

266your environment does not contain libvirt based compute hosts. 

267Use the `nova-manage libvirt list_unset_machine_type` command to list these 

268instances. For more details see the following: 

269https://docs.openstack.org/nova/latest/admin/hw-machine-type.html""")) 

270 return upgradecheck.Result(upgradecheck.Code.WARNING, msg) 

271 

272 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

273 

274 def _check_service_user_token(self): 

275 if not CONF.service_user.send_service_user_token: 

276 msg = (_(""" 

277Service user token configuration is required for all Nova services. 

278For more details see the following: 

279https://docs.openstack.org/nova/latest/admin/configuration/service-user-token.html""")) # noqa 

280 return upgradecheck.Result(upgradecheck.Code.FAILURE, msg) 

281 return upgradecheck.Result(upgradecheck.Code.SUCCESS) 

282 

283 # The format of the check functions is to return an upgradecheck.Result 

284 # object with the appropriate upgradecheck.Code and details set. If the 

285 # check hits warnings or failures then those should be stored in the 

286 # returned upgradecheck.Result's "details" attribute. The summary will 

287 # be rolled up at the end of the check() function. These functions are 

288 # intended to be run in order and build on top of each other so order 

289 # matters. 

290 _upgrade_checks = ( 

291 # Added in Ocata 

292 (_('Cells v2'), _check_cellsv2), 

293 # Added in Ocata 

294 (_('Placement API'), _check_placement), 

295 # Added in Train 

296 (_('Cinder API'), _check_cinder), 

297 # Added in Victoria 

298 ( 

299 _('Policy File JSON to YAML Migration'), 

300 (common_checks.check_policy_json, {'conf': CONF}) 

301 ), 

302 # Added in Wallaby 

303 (_('Older than N-1 computes'), _check_old_computes), 

304 # Added in Wallaby 

305 (_('hw_machine_type unset'), _check_machine_type_set), 

306 # Added in Bobcat 

307 (_('Service User Token Configuration'), _check_service_user_token), 

308 ) 

309 

310 

311CATEGORIES = { 

312 'upgrade': UpgradeCommands, 

313} 

314 

315 

316add_command_parsers = functools.partial(cmd_common.add_command_parsers, 

317 categories=CATEGORIES) 

318 

319 

320category_opt = cfg.SubCommandOpt('category', 

321 title='Command categories', 

322 help='Available categories', 

323 handler=add_command_parsers) 

324 

325 

326def main(): 

327 """Parse options and call the appropriate class/method.""" 

328 CONF.register_cli_opt(category_opt) 

329 config.parse_args(sys.argv) 

330 

331 if CONF.category.name == "version": 

332 print(version.version_string_with_package()) 

333 return 0 

334 

335 if CONF.category.name == "bash-completion": 

336 cmd_common.print_bash_completion(CATEGORIES) 

337 return 0 

338 

339 try: 

340 fn, fn_args, fn_kwargs = cmd_common.get_action_fn() 

341 ret = fn(*fn_args, **fn_kwargs) 

342 return ret 

343 except Exception: 

344 print(_('Error:\n%s') % traceback.format_exc()) 

345 # This is 255 so it's not confused with the upgrade check exit codes. 

346 return 255