Juju Charm - Nova Cloud Controller
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

nova_cc_utils.py 58KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690
  1. # Copyright 2016 Canonical Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain 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,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import os
  15. import shutil
  16. import subprocess
  17. import ConfigParser
  18. from base64 import b64encode
  19. from collections import OrderedDict
  20. from copy import deepcopy
  21. from charmhelpers.contrib.openstack import context, templating
  22. from charmhelpers.contrib.hahelpers.cluster import (
  23. get_hacluster_config,
  24. )
  25. from charmhelpers.contrib.peerstorage import (
  26. peer_retrieve,
  27. peer_store,
  28. )
  29. from charmhelpers.contrib.python.packages import (
  30. pip_install,
  31. )
  32. from charmhelpers.contrib.openstack.utils import (
  33. configure_installation_source,
  34. get_host_ip,
  35. get_hostname,
  36. get_os_codename_install_source,
  37. git_clone_and_install,
  38. git_default_repos,
  39. git_generate_systemd_init_files,
  40. git_install_requested,
  41. git_pip_venv_dir,
  42. git_src_dir,
  43. git_yaml_value,
  44. incomplete_relation_data,
  45. is_ip,
  46. os_release,
  47. reset_os_release,
  48. save_script_rc as _save_script_rc,
  49. is_unit_paused_set,
  50. make_assess_status_func,
  51. pause_unit,
  52. resume_unit,
  53. os_application_version_set,
  54. token_cache_pkgs,
  55. enable_memcache,
  56. CompareOpenStackReleases,
  57. )
  58. from charmhelpers.fetch import (
  59. apt_upgrade,
  60. apt_update,
  61. apt_install,
  62. add_source,
  63. filter_installed_packages
  64. )
  65. from charmhelpers.core.hookenv import (
  66. charm_dir,
  67. config,
  68. is_leader,
  69. log,
  70. relation_get,
  71. relation_ids,
  72. remote_unit,
  73. DEBUG,
  74. INFO,
  75. ERROR,
  76. status_set,
  77. related_units,
  78. local_unit,
  79. )
  80. from charmhelpers.core.host import (
  81. adduser,
  82. add_group,
  83. add_user_to_group,
  84. mkdir,
  85. service,
  86. service_pause,
  87. service_resume,
  88. service_running,
  89. service_start,
  90. service_stop,
  91. lsb_release,
  92. CompareHostReleases,
  93. )
  94. from charmhelpers.core.templating import render
  95. from charmhelpers.contrib.network.ip import (
  96. is_ipv6,
  97. ns_query,
  98. )
  99. from charmhelpers.core.decorators import (
  100. retry_on_exception,
  101. )
  102. import nova_cc_context
  103. TEMPLATES = 'templates/'
  104. CLUSTER_RES = 'grp_nova_vips'
  105. # The interface is said to be satisfied if anyone of the interfaces in the
  106. # list has a complete context.
  107. REQUIRED_INTERFACES = {
  108. 'database': ['shared-db', 'pgsql-db'],
  109. 'messaging': ['amqp', 'zeromq-configuration'],
  110. 'identity': ['identity-service'],
  111. 'image': ['image-service'],
  112. 'compute': ['nova-compute'],
  113. }
  114. # removed from original: charm-helper-sh
  115. BASE_PACKAGES = [
  116. 'apache2',
  117. 'haproxy',
  118. 'libapache2-mod-wsgi',
  119. 'python-keystoneclient',
  120. 'python-mysqldb',
  121. 'python-psycopg2',
  122. 'python-psutil',
  123. 'python-six',
  124. 'uuid',
  125. 'python-memcache',
  126. ]
  127. VERSION_PACKAGE = 'nova-common'
  128. BASE_GIT_PACKAGES = [
  129. 'libffi-dev',
  130. 'libmysqlclient-dev',
  131. 'libssl-dev',
  132. 'libxml2-dev',
  133. 'libxslt1-dev',
  134. 'libyaml-dev',
  135. 'openstack-pkg-tools',
  136. 'python-dev',
  137. 'python-pip',
  138. 'python-setuptools',
  139. 'zlib1g-dev',
  140. ]
  141. LATE_GIT_PACKAGES = [
  142. 'novnc',
  143. 'spice-html5',
  144. 'websockify',
  145. ]
  146. # ubuntu packages that should not be installed when deploying from git
  147. GIT_PACKAGE_BLACKLIST = [
  148. 'neutron-common',
  149. 'neutron-server',
  150. 'neutron-plugin-ml2',
  151. 'nova-api-ec2',
  152. 'nova-api-os-compute',
  153. 'nova-api-os-volume',
  154. 'nova-cert',
  155. 'nova-conductor',
  156. 'nova-consoleauth',
  157. 'nova-novncproxy',
  158. 'nova-objectstore',
  159. 'nova-scheduler',
  160. 'nova-spiceproxy',
  161. 'nova-xvpvncproxy',
  162. 'python-keystoneclient',
  163. 'python-six',
  164. 'quantum-server',
  165. ]
  166. BASE_SERVICES = [
  167. 'nova-api-ec2',
  168. 'nova-api-os-compute',
  169. 'nova-placement-api',
  170. 'nova-objectstore',
  171. 'nova-cert',
  172. 'nova-scheduler',
  173. 'nova-conductor',
  174. ]
  175. AWS_COMPAT_SERVICES = ['nova-api-ec2', 'nova-objectstore']
  176. SERVICE_BLACKLIST = {
  177. 'liberty': AWS_COMPAT_SERVICES,
  178. 'newton': ['nova-cert'],
  179. }
  180. API_PORTS = {
  181. 'nova-api-ec2': 8773,
  182. 'nova-api-os-compute': 8774,
  183. 'nova-api-os-volume': 8776,
  184. 'nova-placement-api': 8778,
  185. 'nova-objectstore': 3333,
  186. }
  187. NOVA_CONF_DIR = "/etc/nova"
  188. NEUTRON_CONF_DIR = "/etc/neutron"
  189. NOVA_CONF = '%s/nova.conf' % NOVA_CONF_DIR
  190. NOVA_API_PASTE = '%s/api-paste.ini' % NOVA_CONF_DIR
  191. HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
  192. APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
  193. APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
  194. MEMCACHED_CONF = '/etc/memcached.conf'
  195. WSGI_NOVA_PLACEMENT_API_CONF = \
  196. '/etc/apache2/sites-enabled/wsgi-openstack-api.conf'
  197. PACKAGE_NOVA_PLACEMENT_API_CONF = \
  198. '/etc/apache2/sites-enabled/nova-placement-api.conf'
  199. def resolve_services():
  200. _services = deepcopy(BASE_SERVICES)
  201. os_rel = os_release('nova-common')
  202. for release in SERVICE_BLACKLIST:
  203. if os_rel >= release or config('disable-aws-compat'):
  204. [_services.remove(service)
  205. for service in SERVICE_BLACKLIST[release]]
  206. return _services
  207. BASE_RESOURCE_MAP = OrderedDict([
  208. (NOVA_CONF, {
  209. 'services': resolve_services(),
  210. 'contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
  211. context.SharedDBContext(
  212. relation_prefix='nova', ssl_dir=NOVA_CONF_DIR),
  213. context.OSConfigFlagContext(
  214. charm_flag='nova-alchemy-flags',
  215. template_flag='nova_alchemy_flags'),
  216. nova_cc_context.NovaPostgresqlDBContext(),
  217. context.ImageServiceContext(),
  218. context.OSConfigFlagContext(),
  219. context.SubordinateConfigContext(
  220. interface='nova-vmware',
  221. service='nova',
  222. config_file=NOVA_CONF),
  223. nova_cc_context.NovaCellContext(),
  224. context.SyslogContext(),
  225. context.LogLevelContext(),
  226. nova_cc_context.HAProxyContext(),
  227. nova_cc_context.IdentityServiceContext(
  228. service='nova',
  229. service_user='nova'),
  230. nova_cc_context.VolumeServiceContext(),
  231. context.ZeroMQContext(),
  232. context.NotificationDriverContext(),
  233. nova_cc_context.NovaIPv6Context(),
  234. nova_cc_context.NeutronCCContext(),
  235. nova_cc_context.NovaConfigContext(),
  236. nova_cc_context.InstanceConsoleContext(),
  237. nova_cc_context.ConsoleSSLContext(),
  238. nova_cc_context.CloudComputeContext(),
  239. context.InternalEndpointContext('nova-common'),
  240. nova_cc_context.NeutronAPIContext(),
  241. nova_cc_context.SerialConsoleContext(),
  242. context.MemcacheContext()],
  243. }),
  244. (NOVA_API_PASTE, {
  245. 'services': [s for s in resolve_services() if 'api' in s],
  246. 'contexts': [nova_cc_context.IdentityServiceContext(),
  247. nova_cc_context.APIRateLimitingContext()],
  248. }),
  249. (HAPROXY_CONF, {
  250. 'contexts': [context.HAProxyContext(singlenode_mode=True),
  251. nova_cc_context.HAProxyContext()],
  252. 'services': ['haproxy'],
  253. }),
  254. (APACHE_CONF, {
  255. 'contexts': [nova_cc_context.ApacheSSLContext()],
  256. 'services': ['apache2'],
  257. }),
  258. (APACHE_24_CONF, {
  259. 'contexts': [nova_cc_context.ApacheSSLContext()],
  260. 'services': ['apache2'],
  261. }),
  262. ])
  263. CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
  264. NOVA_SSH_DIR = '/etc/nova/compute_ssh/'
  265. CONSOLE_CONFIG = {
  266. 'spice': {
  267. 'packages': ['nova-spiceproxy', 'nova-consoleauth'],
  268. 'services': ['nova-spiceproxy', 'nova-consoleauth'],
  269. 'proxy-page': '/spice_auto.html',
  270. 'proxy-port': 6082,
  271. },
  272. 'novnc': {
  273. 'packages': ['nova-novncproxy', 'nova-consoleauth'],
  274. 'services': ['nova-novncproxy', 'nova-consoleauth'],
  275. 'proxy-page': '/vnc_auto.html',
  276. 'proxy-port': 6080,
  277. },
  278. 'xvpvnc': {
  279. 'packages': ['nova-xvpvncproxy', 'nova-consoleauth'],
  280. 'services': ['nova-xvpvncproxy', 'nova-consoleauth'],
  281. 'proxy-page': '/console',
  282. 'proxy-port': 6081,
  283. },
  284. }
  285. SERIAL_CONSOLE = {
  286. 'packages': ['nova-serialproxy', 'nova-consoleauth',
  287. 'websockify'],
  288. 'services': ['nova-serialproxy', 'nova-consoleauth'],
  289. }
  290. def resource_map(actual_services=True):
  291. '''
  292. Dynamically generate a map of resources that will be managed for a single
  293. hook execution.
  294. :param actual_services: Whether to return the actual services that run on a
  295. unit (ie. apache2) or the services defined in BASE_SERVICES
  296. (ie.nova-placement-api).
  297. '''
  298. resource_map = deepcopy(BASE_RESOURCE_MAP)
  299. if os.path.exists('/etc/apache2/conf-available'):
  300. resource_map.pop(APACHE_CONF)
  301. else:
  302. resource_map.pop(APACHE_24_CONF)
  303. resource_map[NOVA_CONF]['contexts'].append(
  304. nova_cc_context.NeutronCCContext())
  305. release = os_release('nova-common')
  306. cmp_os_release = CompareOpenStackReleases(release)
  307. if cmp_os_release >= 'mitaka':
  308. resource_map[NOVA_CONF]['contexts'].append(
  309. nova_cc_context.NovaAPISharedDBContext(relation_prefix='novaapi',
  310. database='nova_api',
  311. ssl_dir=NOVA_CONF_DIR)
  312. )
  313. if console_attributes('services'):
  314. resource_map[NOVA_CONF]['services'] += console_attributes('services')
  315. # nova-consoleauth will be managed by pacemaker, if
  316. # single-nova-consoleauth is used, then don't monitor for the
  317. # nova-consoleauth service to be started (LP: #1660244).
  318. if config('single-nova-consoleauth') and relation_ids('ha'):
  319. services = resource_map[NOVA_CONF]['services']
  320. if 'nova-consoleauth' in services:
  321. services.remove('nova-consoleauth')
  322. if (config('enable-serial-console') and cmp_os_release >= 'juno'):
  323. resource_map[NOVA_CONF]['services'] += SERIAL_CONSOLE['services']
  324. # also manage any configs that are being updated by subordinates.
  325. vmware_ctxt = context.SubordinateConfigContext(interface='nova-vmware',
  326. service='nova',
  327. config_file=NOVA_CONF)
  328. vmware_ctxt = vmware_ctxt()
  329. if vmware_ctxt and 'services' in vmware_ctxt:
  330. for s in vmware_ctxt['services']:
  331. if s not in resource_map[NOVA_CONF]['services']:
  332. resource_map[NOVA_CONF]['services'].append(s)
  333. if enable_memcache(release=release):
  334. resource_map[MEMCACHED_CONF] = {
  335. 'contexts': [context.MemcacheContext()],
  336. 'services': ['memcached']}
  337. if actual_services and placement_api_enabled():
  338. for cfile in resource_map:
  339. svcs = resource_map[cfile]['services']
  340. if 'nova-placement-api' in svcs:
  341. svcs.remove('nova-placement-api')
  342. if 'apache2' not in svcs:
  343. svcs.append('apache2')
  344. wsgi_script = "/usr/bin/nova-placement-api"
  345. resource_map[WSGI_NOVA_PLACEMENT_API_CONF] = {
  346. 'contexts': [context.WSGIWorkerConfigContext(name="nova",
  347. script=wsgi_script),
  348. nova_cc_context.HAProxyContext()],
  349. 'services': ['apache2']
  350. }
  351. elif not placement_api_enabled():
  352. for cfile in resource_map:
  353. svcs = resource_map[cfile]['services']
  354. if 'nova-placement-api' in svcs:
  355. svcs.remove('nova-placement-api')
  356. return resource_map
  357. def register_configs(release=None):
  358. release = release or os_release('nova-common')
  359. configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
  360. openstack_release=release)
  361. for cfg, rscs in resource_map().iteritems():
  362. configs.register(cfg, rscs['contexts'])
  363. return configs
  364. def restart_map(actual_services=True):
  365. '''
  366. Constructs a restart map of config files and corresponding services
  367. :param actual_services: Whether to return the actual services that run on a
  368. unit (ie. apache2) or the services defined in BASE_SERVICES
  369. (ie.nova-placement-api).
  370. '''
  371. return OrderedDict(
  372. [(cfg, v['services'])
  373. for cfg, v in resource_map(actual_services).iteritems()
  374. if v['services']])
  375. def services():
  376. ''' Returns a list of services associate with this charm '''
  377. _services = []
  378. for v in restart_map().values():
  379. _services = _services + v
  380. return list(set(_services))
  381. def determine_ports():
  382. '''Assemble a list of API ports for services we are managing'''
  383. ports = []
  384. for services in restart_map(actual_services=False).values():
  385. for svc in services:
  386. try:
  387. ports.append(API_PORTS[svc])
  388. except KeyError:
  389. pass
  390. return list(set(ports))
  391. def api_port(service):
  392. return API_PORTS[service]
  393. def console_attributes(attr, proto=None):
  394. '''Leave proto unset to query attributes of the protocal specified at
  395. runtime'''
  396. if proto:
  397. console_proto = proto
  398. else:
  399. console_proto = config('console-access-protocol')
  400. if console_proto is not None and console_proto.lower() in ('none', ''):
  401. console_proto = None
  402. if attr == 'protocol':
  403. return console_proto
  404. # 'vnc' is a virtual type made up of novnc and xvpvnc
  405. if console_proto == 'vnc':
  406. if attr in ['packages', 'services']:
  407. return list(set(CONSOLE_CONFIG['novnc'][attr] +
  408. CONSOLE_CONFIG['xvpvnc'][attr]))
  409. else:
  410. return None
  411. if console_proto in CONSOLE_CONFIG:
  412. return CONSOLE_CONFIG[console_proto][attr]
  413. return None
  414. def determine_packages():
  415. # currently all packages match service names
  416. packages = deepcopy(BASE_PACKAGES)
  417. for v in resource_map(actual_services=False).values():
  418. packages.extend(v['services'])
  419. if console_attributes('packages'):
  420. packages.extend(console_attributes('packages'))
  421. if (config('enable-serial-console') and
  422. CompareOpenStackReleases(os_release('nova-common')) >= 'juno'):
  423. packages.extend(SERIAL_CONSOLE['packages'])
  424. if git_install_requested():
  425. packages = list(set(packages))
  426. packages.extend(BASE_GIT_PACKAGES)
  427. # don't include packages that will be installed from git
  428. for p in GIT_PACKAGE_BLACKLIST:
  429. if p in packages:
  430. packages.remove(p)
  431. packages.extend(token_cache_pkgs(source=config('openstack-origin')))
  432. return list(set(packages))
  433. def save_script_rc():
  434. env_vars = {
  435. 'OPENSTACK_PORT_MCASTPORT': config('ha-mcastport'),
  436. 'OPENSTACK_SERVICE_API_EC2': 'nova-api-ec2',
  437. 'OPENSTACK_SERVICE_API_OS_COMPUTE': 'nova-api-os-compute',
  438. 'OPENSTACK_SERVICE_CERT': 'nova-cert',
  439. 'OPENSTACK_SERVICE_CONDUCTOR': 'nova-conductor',
  440. 'OPENSTACK_SERVICE_OBJECTSTORE': 'nova-objectstore',
  441. 'OPENSTACK_SERVICE_SCHEDULER': 'nova-scheduler',
  442. }
  443. if relation_ids('nova-volume-service'):
  444. env_vars['OPENSTACK_SERVICE_API_OS_VOL'] = 'nova-api-os-volume'
  445. _save_script_rc(**env_vars)
  446. def get_step_upgrade_source(new_src):
  447. '''
  448. Determine if upgrade skips a release and, if so, return source
  449. of skipped release.
  450. '''
  451. sources = {
  452. # target_src: (cur_pocket, step_src)
  453. # NOTE: cur_pocket == * means all upgrades to target_src must step
  454. # through step_src if step_src is higher than
  455. # current release
  456. 'precise-icehouse': ('precise-updates/grizzly',
  457. 'cloud:precise-havana'),
  458. 'precise-icehouse/proposed': ('precise-proposed/grizzly',
  459. 'cloud:precise-havana/proposed'),
  460. 'trusty-liberty': ('*', 'cloud:trusty-kilo'),
  461. 'xenial-ocata': ('*', 'cloud:xenial-newton'), # LP: #1711209
  462. }
  463. try:
  464. os_codename = get_os_codename_install_source(new_src)
  465. ubuntu_series = lsb_release()['DISTRIB_CODENAME'].lower()
  466. cur_pocket, step_src = sources['%s-%s' % (ubuntu_series, os_codename)]
  467. current_src = os_release('nova-common')
  468. step_src_codename = get_os_codename_install_source(step_src)
  469. if cur_pocket == '*' and step_src_codename > current_src:
  470. return step_src
  471. except KeyError:
  472. pass
  473. configure_installation_source(new_src)
  474. # charmhelpers.contrib.openstack.utils.configure_installation_source()
  475. # configures the repository in juju_deb.list, while
  476. # charmhelpers.fetch.add_sources() uses cloud-archive.list, so both
  477. # files need to read looking for the currently configured repo.
  478. for fname in ['cloud-archive.list', 'juju_deb.list']:
  479. fpath = os.path.join('/etc/apt/sources.list.d/', fname)
  480. if not os.path.isfile(fpath):
  481. log('Missing %s skipping it' % fpath, level=DEBUG)
  482. continue
  483. with open(fpath, 'r') as f:
  484. for line in f.readlines():
  485. for target_src, (cur_pocket, step_src) in sources.items():
  486. if target_src != new_src:
  487. continue
  488. if cur_pocket in line:
  489. return step_src
  490. return None
  491. POLICY_RC_D = """#!/bin/bash
  492. set -e
  493. case $1 in
  494. nova-*)
  495. [ $2 = "start" ] && exit 101
  496. ;;
  497. *)
  498. ;;
  499. esac
  500. exit 0
  501. """
  502. def enable_policy_rcd():
  503. with open('/usr/sbin/policy-rc.d', 'w') as policy:
  504. policy.write(POLICY_RC_D)
  505. os.chmod('/usr/sbin/policy-rc.d', 0o755)
  506. def disable_policy_rcd():
  507. os.unlink('/usr/sbin/policy-rc.d')
  508. def is_db_initialised():
  509. if relation_ids('cluster'):
  510. dbsync_state = peer_retrieve('dbsync_state')
  511. if dbsync_state == 'complete':
  512. log("Database is initialised", level=DEBUG)
  513. return True
  514. log("Database is NOT initialised", level=DEBUG)
  515. return False
  516. def is_cellv2_init_ready():
  517. """Determine if we're ready to initialize the cell v2 databases
  518. Cells v2 init requires transport_url and database connections to be set
  519. in nova.conf.
  520. """
  521. amqp = context.AMQPContext()
  522. shared_db = nova_cc_context.NovaCellV2SharedDBContext()
  523. if (CompareOpenStackReleases(os_release('nova-common')) >= 'ocata' and
  524. amqp() and shared_db()):
  525. return True
  526. log("OpenStack release, database, or rabbitmq not ready for Cells V2",
  527. level=DEBUG)
  528. return False
  529. def _do_openstack_upgrade(new_src):
  530. enable_policy_rcd()
  531. # All upgrades to Liberty are forced to step through Kilo. Liberty does
  532. # not have the migrate_flavor_data option (Bug #1511466) available so it
  533. # must be done pre-upgrade
  534. if (CompareOpenStackReleases(os_release('nova-common')) == 'kilo' and
  535. is_leader()):
  536. migrate_nova_flavors()
  537. # 'nova-manage db online_data_migrations' needs to be run before moving to
  538. # the next release for environments upgraded using old charms where this
  539. # step was not being executed (LP: #1711209).
  540. online_data_migrations_if_needed()
  541. new_os_rel = get_os_codename_install_source(new_src)
  542. cmp_new_os_rel = CompareOpenStackReleases(new_os_rel)
  543. log('Performing OpenStack upgrade to %s.' % (new_os_rel))
  544. configure_installation_source(new_src)
  545. dpkg_opts = [
  546. '--option', 'Dpkg::Options::=--force-confnew',
  547. '--option', 'Dpkg::Options::=--force-confdef',
  548. ]
  549. apt_update(fatal=True)
  550. apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
  551. reset_os_release()
  552. apt_install(determine_packages(), fatal=True)
  553. disable_policy_rcd()
  554. # NOTE(jamespage) upgrade with existing config files as the
  555. # havana->icehouse migration enables new service_plugins which
  556. # create issues with db upgrades
  557. configs = register_configs(release=new_os_rel)
  558. configs.write_all()
  559. if cmp_new_os_rel >= 'mitaka' and not database_setup(prefix='novaapi'):
  560. # NOTE: Defer service restarts and database migrations for now
  561. # as nova_api database is not yet created
  562. if (relation_ids('cluster') and is_leader()):
  563. # NOTE: reset dbsync state so that migration will complete
  564. # when the nova_api database is setup.
  565. peer_store('dbsync_state', None)
  566. return configs
  567. if cmp_new_os_rel >= 'ocata' and not database_setup(prefix='novacell0'):
  568. # NOTE: Defer service restarts and database migrations for now
  569. # as nova_cell0 database is not yet created
  570. if (relation_ids('cluster') and is_leader()):
  571. # NOTE: reset dbsync state so that migration will complete
  572. # when the novacell0 database is setup.
  573. peer_store('dbsync_state', None)
  574. return configs
  575. if is_leader():
  576. status_set('maintenance', 'Running nova db migration')
  577. migrate_nova_databases()
  578. if not is_unit_paused_set():
  579. [service_start(s) for s in services()]
  580. return configs
  581. def database_setup(prefix):
  582. '''
  583. Determine when a specific database is setup
  584. and access is granted to the local unit.
  585. This function only checks the MySQL shared-db
  586. relation name using the provided prefix.
  587. '''
  588. key = '{}_allowed_units'.format(prefix)
  589. for db_rid in relation_ids('shared-db'):
  590. for unit in related_units(db_rid):
  591. allowed_units = relation_get(key, rid=db_rid, unit=unit)
  592. if allowed_units and local_unit() in allowed_units.split():
  593. return True
  594. return False
  595. def do_openstack_upgrade(configs):
  596. new_src = config('openstack-origin')
  597. step_src = get_step_upgrade_source(new_src)
  598. if step_src is not None:
  599. _do_openstack_upgrade(step_src)
  600. return _do_openstack_upgrade(new_src)
  601. @retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
  602. def migrate_nova_flavors():
  603. '''Runs nova-manage to migrate flavor data if needed'''
  604. log('Migrating nova flavour information in database.', level=INFO)
  605. cmd = ['nova-manage', 'db', 'migrate_flavor_data']
  606. try:
  607. subprocess.check_output(cmd)
  608. except subprocess.CalledProcessError as e:
  609. log('migrate_flavor_data failed\n{}'.format(e.output), level=ERROR)
  610. raise
  611. @retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
  612. def online_data_migrations_if_needed():
  613. '''Runs nova-manage to run online data migrations available since Mitaka'''
  614. if (is_leader() and
  615. CompareOpenStackReleases(os_release('nova-common')) >= 'mitaka'):
  616. log('Running online_data_migrations', level=INFO)
  617. cmd = ['nova-manage', 'db', 'online_data_migrations']
  618. try:
  619. subprocess.check_output(cmd)
  620. except subprocess.CalledProcessError as e:
  621. log('online_data_migrations failed\n{}'.format(e.output),
  622. level=ERROR)
  623. raise
  624. def migrate_nova_api_database():
  625. '''Initialize or migrate the nova_api database'''
  626. if CompareOpenStackReleases(os_release('nova-common')) >= 'mitaka':
  627. log('Migrating the nova-api database.', level=INFO)
  628. cmd = ['nova-manage', 'api_db', 'sync']
  629. try:
  630. subprocess.check_output(cmd)
  631. except subprocess.CalledProcessError as e:
  632. # NOTE(coreycb): sync of api_db on upgrade from newton->ocata
  633. # fails but cell init is successful.
  634. log('Ignoring CalledProcessError during nova-api database '
  635. 'migration\n{}'.format(e.output), level=INFO)
  636. def migrate_nova_database():
  637. '''Initialize or migrate the nova database'''
  638. log('Migrating the nova database.', level=INFO)
  639. cmd = ['nova-manage', 'db', 'sync']
  640. try:
  641. subprocess.check_output(cmd)
  642. except subprocess.CalledProcessError as e:
  643. log('db sync failed\n{}'.format(e.output), level=ERROR)
  644. raise
  645. def initialize_cell_databases():
  646. '''Initialize the cell0 and cell1 databases
  647. cell0 is stored in the database named 'nova_cell0'.
  648. cell1 is stored in the database named 'nova'.
  649. '''
  650. log('Creating cell0 database records', level=INFO)
  651. cmd = ['nova-manage', 'cell_v2', 'map_cell0']
  652. try:
  653. subprocess.check_output(cmd)
  654. except subprocess.CalledProcessError as e:
  655. log('map_cell0 failed\n{}'.format(e.output), level=ERROR)
  656. raise
  657. log('Creating cell1 database records', level=INFO)
  658. cmd = ['nova-manage', 'cell_v2', 'create_cell', '--name', 'cell1',
  659. '--verbose']
  660. try:
  661. subprocess.check_output(cmd)
  662. log('cell1 was successfully created', level=INFO)
  663. except subprocess.CalledProcessError as e:
  664. if e.returncode == 1:
  665. log('Cell1 create_cell failed\n{}'.format(e.output), level=ERROR)
  666. raise
  667. elif e.returncode == 2:
  668. log('Cell1 create_cell failure ignored - a cell is already using '
  669. 'the transport_url/database combination.', level=INFO)
  670. def get_cell_uuid(cell):
  671. '''Get cell uuid
  672. :param cell: string cell name i.e. 'cell1'
  673. :returns: string cell uuid
  674. '''
  675. log("Listing cell, '{}'".format(cell), level=INFO)
  676. cmd = ['sudo', 'nova-manage', 'cell_v2', 'list_cells']
  677. try:
  678. out = subprocess.check_output(cmd)
  679. except subprocess.CalledProcessError as e:
  680. log('list_cells failed\n{}'.format(e.output), level=ERROR)
  681. raise
  682. cell_uuid = out.split(cell, 1)[1].split()[1]
  683. if not cell_uuid:
  684. raise Exception("Cannot find cell, '{}', in list_cells."
  685. "".format(cell))
  686. return cell_uuid
  687. def update_cell_database():
  688. '''Update the cell1 database properties
  689. This should be called whenever a database or rabbitmq-server relation is
  690. changed to update the transport_url in the nova_api cell_mappings table.
  691. '''
  692. log('Updating cell1 properties', level=INFO)
  693. cell1_uuid = get_cell_uuid('cell1')
  694. cmd = ['nova-manage', 'cell_v2', 'update_cell', '--cell_uuid', cell1_uuid]
  695. try:
  696. subprocess.check_output(cmd)
  697. except subprocess.CalledProcessError as e:
  698. if e.returncode == 1:
  699. log('Cell1 update_cell failed\n{}'.format(e.output), level=ERROR)
  700. raise
  701. elif e.returncode == 2:
  702. log('Cell1 update_cell failure ignored - the properties cannot '
  703. 'be set.', level=INFO)
  704. else:
  705. log('cell1 was successfully updated', level=INFO)
  706. def map_instances():
  707. '''Map instances to cell
  708. Updates nova_api.instance_mappings with pre-existing instances
  709. '''
  710. log('Cell1 map_instances', level=INFO)
  711. cell1_uuid = get_cell_uuid('cell1')
  712. cmd = ['nova-manage', 'cell_v2', 'map_instances',
  713. '--cell_uuid', cell1_uuid]
  714. try:
  715. subprocess.check_output(cmd)
  716. except subprocess.CalledProcessError as e:
  717. log('Cell1 map_instances failed\n{}'.format(e.output), level=ERROR)
  718. raise
  719. def add_hosts_to_cell():
  720. '''Map compute hosts to cell'''
  721. log('Cell1 discover_hosts', level=INFO)
  722. cell1_uuid = get_cell_uuid('cell1')
  723. cmd = ['nova-manage', 'cell_v2', 'discover_hosts', '--cell_uuid',
  724. cell1_uuid, '--verbose']
  725. try:
  726. subprocess.check_output(cmd)
  727. except subprocess.CalledProcessError as e:
  728. log('Cell1 discover_hosts failed\n{}'.format(e.output), level=ERROR)
  729. raise
  730. def finalize_migrate_nova_databases():
  731. if relation_ids('cluster'):
  732. log('Informing peers that dbsync is complete', level=INFO)
  733. peer_store('dbsync_state', 'complete')
  734. log('Enabling services', level=INFO)
  735. enable_services()
  736. cmd_all_services('start')
  737. # NOTE(jamespage): Retry deals with sync issues during one-shot HA deploys.
  738. # mysql might be restarting or suchlike.
  739. @retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
  740. def migrate_nova_databases():
  741. '''Runs nova-manage to initialize new databases or migrate existing'''
  742. if CompareOpenStackReleases(os_release('nova-common')) < 'ocata':
  743. migrate_nova_api_database()
  744. migrate_nova_database()
  745. online_data_migrations_if_needed()
  746. finalize_migrate_nova_databases()
  747. elif is_cellv2_init_ready():
  748. migrate_nova_api_database()
  749. initialize_cell_databases()
  750. migrate_nova_database()
  751. online_data_migrations_if_needed()
  752. add_hosts_to_cell()
  753. map_instances()
  754. finalize_migrate_nova_databases()
  755. # TODO: refactor to use unit storage or related data
  756. def auth_token_config(setting):
  757. """
  758. Returns currently configured value for setting in api-paste.ini's
  759. authtoken section, or None.
  760. """
  761. config = ConfigParser.RawConfigParser()
  762. config.read('/etc/nova/api-paste.ini')
  763. try:
  764. value = config.get('filter:authtoken', setting)
  765. except:
  766. return None
  767. if value.startswith('%'):
  768. return None
  769. return value
  770. def keystone_ca_cert_b64():
  771. '''Returns the local Keystone-provided CA cert if it exists, or None.'''
  772. if not os.path.isfile(CA_CERT_PATH):
  773. return None
  774. with open(CA_CERT_PATH) as _in:
  775. return b64encode(_in.read())
  776. def ssh_directory_for_unit(unit=None, user=None):
  777. if unit:
  778. remote_service = unit.split('/')[0]
  779. else:
  780. remote_service = remote_unit().split('/')[0]
  781. if user:
  782. remote_service = "{}_{}".format(remote_service, user)
  783. _dir = os.path.join(NOVA_SSH_DIR, remote_service)
  784. for d in [NOVA_SSH_DIR, _dir]:
  785. if not os.path.isdir(d):
  786. os.mkdir(d)
  787. for f in ['authorized_keys', 'known_hosts']:
  788. f = os.path.join(_dir, f)
  789. if not os.path.isfile(f):
  790. open(f, 'w').close()
  791. return _dir
  792. def known_hosts(unit=None, user=None):
  793. return os.path.join(ssh_directory_for_unit(unit, user), 'known_hosts')
  794. def authorized_keys(unit=None, user=None):
  795. return os.path.join(ssh_directory_for_unit(unit, user), 'authorized_keys')
  796. def ssh_known_host_key(host, unit=None, user=None):
  797. cmd = ['ssh-keygen', '-f', known_hosts(unit, user), '-H', '-F', host]
  798. try:
  799. # The first line of output is like '# Host xx found: line 1 type RSA',
  800. # which should be excluded.
  801. output = subprocess.check_output(cmd).strip()
  802. except subprocess.CalledProcessError:
  803. return None
  804. if output:
  805. # Bug #1500589 cmd has 0 rc on precise if entry not present
  806. lines = output.split('\n')
  807. if len(lines) > 1:
  808. return lines[1]
  809. return None
  810. def remove_known_host(host, unit=None, user=None):
  811. log('Removing SSH known host entry for compute host at %s' % host)
  812. cmd = ['ssh-keygen', '-f', known_hosts(unit, user), '-R', host]
  813. subprocess.check_call(cmd)
  814. def is_same_key(key_1, key_2):
  815. # The key format get will be like '|1|2rUumCavEXWVaVyB5uMl6m85pZo=|Cp'
  816. # 'EL6l7VTY37T/fg/ihhNb/GPgs= ssh-rsa AAAAB', we only need to compare
  817. # the part start with 'ssh-rsa' followed with '= ', because the hash
  818. # value in the beginning will change each time.
  819. k_1 = key_1.split('= ')[1]
  820. k_2 = key_2.split('= ')[1]
  821. return k_1 == k_2
  822. def add_known_host(host, unit=None, user=None):
  823. '''Add variations of host to a known hosts file.'''
  824. cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host]
  825. try:
  826. remote_key = subprocess.check_output(cmd).strip()
  827. except Exception as e:
  828. log('Could not obtain SSH host key from %s' % host, level=ERROR)
  829. raise e
  830. current_key = ssh_known_host_key(host, unit, user)
  831. if current_key and remote_key:
  832. if is_same_key(remote_key, current_key):
  833. log('Known host key for compute host %s up to date.' % host)
  834. return
  835. else:
  836. remove_known_host(host, unit, user)
  837. log('Adding SSH host key to known hosts for compute node at %s.' % host)
  838. with open(known_hosts(unit, user), 'a') as out:
  839. out.write(remote_key + '\n')
  840. def ssh_authorized_key_exists(public_key, unit=None, user=None):
  841. with open(authorized_keys(unit, user)) as keys:
  842. return (' %s ' % public_key) in keys.read()
  843. def add_authorized_key(public_key, unit=None, user=None):
  844. with open(authorized_keys(unit, user), 'a') as keys:
  845. keys.write(public_key + '\n')
  846. def ssh_compute_add(public_key, rid=None, unit=None, user=None):
  847. # If remote compute node hands us a hostname, ensure we have a
  848. # known hosts entry for its IP, hostname and FQDN.
  849. private_address = relation_get(rid=rid, unit=unit,
  850. attribute='private-address')
  851. hosts = [private_address]
  852. if not is_ipv6(private_address):
  853. if relation_get('hostname'):
  854. hosts.append(relation_get('hostname'))
  855. if not is_ip(private_address):
  856. hosts.append(get_host_ip(private_address))
  857. short = private_address.split('.')[0]
  858. if ns_query(short):
  859. hosts.append(short)
  860. else:
  861. hn = get_hostname(private_address)
  862. if hn:
  863. hosts.append(hn)
  864. short = hn.split('.')[0]
  865. if ns_query(short):
  866. hosts.append(short)
  867. for host in list(set(hosts)):
  868. add_known_host(host, unit, user)
  869. if not ssh_authorized_key_exists(public_key, unit, user):
  870. log('Saving SSH authorized key for compute host at %s.' %
  871. private_address)
  872. add_authorized_key(public_key, unit, user)
  873. def ssh_known_hosts_lines(unit=None, user=None):
  874. known_hosts_list = []
  875. with open(known_hosts(unit, user)) as hosts:
  876. for hosts_line in hosts:
  877. if hosts_line.rstrip():
  878. known_hosts_list.append(hosts_line.rstrip())
  879. return(known_hosts_list)
  880. def ssh_authorized_keys_lines(unit=None, user=None):
  881. authorized_keys_list = []
  882. with open(authorized_keys(unit, user)) as keys:
  883. for authkey_line in keys:
  884. if authkey_line.rstrip():
  885. authorized_keys_list.append(authkey_line.rstrip())
  886. return(authorized_keys_list)
  887. def ssh_compute_remove(public_key, unit=None, user=None):
  888. if not (os.path.isfile(authorized_keys(unit, user)) or
  889. os.path.isfile(known_hosts(unit, user))):
  890. return
  891. with open(authorized_keys(unit, user)) as _keys:
  892. keys = [k.strip() for k in _keys.readlines()]
  893. if public_key not in keys:
  894. return
  895. [keys.remove(key) for key in keys if key == public_key]
  896. with open(authorized_keys(unit, user), 'w') as _keys:
  897. keys = '\n'.join(keys)
  898. if not keys.endswith('\n'):
  899. keys += '\n'
  900. _keys.write(keys)
  901. def determine_endpoints(public_url, internal_url, admin_url):
  902. '''Generates a dictionary containing all relevant endpoints to be
  903. passed to keystone as relation settings.'''
  904. region = config('region')
  905. os_rel = os_release('nova-common')
  906. cmp_os_rel = CompareOpenStackReleases(os_rel)
  907. nova_public_url = ('%s:%s/v2/$(tenant_id)s' %
  908. (public_url, api_port('nova-api-os-compute')))
  909. nova_internal_url = ('%s:%s/v2/$(tenant_id)s' %
  910. (internal_url, api_port('nova-api-os-compute')))
  911. nova_admin_url = ('%s:%s/v2/$(tenant_id)s' %
  912. (admin_url, api_port('nova-api-os-compute')))
  913. ec2_public_url = '%s:%s/services/Cloud' % (
  914. public_url, api_port('nova-api-ec2'))
  915. ec2_internal_url = '%s:%s/services/Cloud' % (
  916. internal_url, api_port('nova-api-ec2'))
  917. ec2_admin_url = '%s:%s/services/Cloud' % (admin_url,
  918. api_port('nova-api-ec2'))
  919. s3_public_url = '%s:%s' % (public_url, api_port('nova-objectstore'))
  920. s3_internal_url = '%s:%s' % (internal_url, api_port('nova-objectstore'))
  921. s3_admin_url = '%s:%s' % (admin_url, api_port('nova-objectstore'))
  922. if cmp_os_rel >= 'ocata':
  923. placement_public_url = '%s:%s' % (
  924. public_url, api_port('nova-placement-api'))
  925. placement_internal_url = '%s:%s' % (
  926. internal_url, api_port('nova-placement-api'))
  927. placement_admin_url = '%s:%s' % (
  928. admin_url, api_port('nova-placement-api'))
  929. # the base endpoints
  930. endpoints = {
  931. 'nova_service': 'nova',
  932. 'nova_region': region,
  933. 'nova_public_url': nova_public_url,
  934. 'nova_admin_url': nova_admin_url,
  935. 'nova_internal_url': nova_internal_url,
  936. 'ec2_service': 'ec2',
  937. 'ec2_region': region,
  938. 'ec2_public_url': ec2_public_url,
  939. 'ec2_admin_url': ec2_admin_url,
  940. 'ec2_internal_url': ec2_internal_url,
  941. 's3_service': 's3',
  942. 's3_region': region,
  943. 's3_public_url': s3_public_url,
  944. 's3_admin_url': s3_admin_url,
  945. 's3_internal_url': s3_internal_url,
  946. }
  947. if cmp_os_rel >= 'kilo':
  948. # NOTE(jamespage) drop endpoints for ec2 and s3
  949. # ec2 is deprecated
  950. # s3 is insecure and should die in flames
  951. endpoints.update({
  952. 'ec2_service': None,
  953. 'ec2_region': None,
  954. 'ec2_public_url': None,
  955. 'ec2_admin_url': None,
  956. 'ec2_internal_url': None,
  957. 's3_service': None,
  958. 's3_region': None,
  959. 's3_public_url': None,
  960. 's3_admin_url': None,
  961. 's3_internal_url': None,
  962. })
  963. if cmp_os_rel >= 'ocata':
  964. endpoints.update({
  965. 'placement_service': 'placement',
  966. 'placement_region': region,
  967. 'placement_public_url': placement_public_url,
  968. 'placement_admin_url': placement_admin_url,
  969. 'placement_internal_url': placement_internal_url,
  970. })
  971. return endpoints
  972. def guard_map():
  973. '''Map of services and required interfaces that must be present before
  974. the service should be allowed to start'''
  975. gmap = {}
  976. nova_services = resolve_services()
  977. if os_release('nova-common') not in ['essex', 'folsom']:
  978. nova_services.append('nova-conductor')
  979. nova_interfaces = ['identity-service', 'amqp']
  980. if relation_ids('pgsql-nova-db'):
  981. nova_interfaces.append('pgsql-nova-db')
  982. else:
  983. nova_interfaces.append('shared-db')
  984. for svc in nova_services:
  985. gmap[svc] = nova_interfaces
  986. return gmap
  987. def service_guard(guard_map, contexts, active=False):
  988. '''Inhibit services in guard_map from running unless
  989. required interfaces are found complete in contexts.'''
  990. def wrap(f):
  991. def wrapped_f(*args):
  992. if active is True:
  993. incomplete_services = []
  994. for svc in guard_map:
  995. for interface in guard_map[svc]:
  996. if interface not in contexts.complete_contexts():
  997. incomplete_services.append(svc)
  998. f(*args)
  999. for svc in incomplete_services:
  1000. if service_running(svc):
  1001. log('Service {} has unfulfilled '
  1002. 'interface requirements, stopping.'.format(svc))
  1003. service_stop(svc)
  1004. else:
  1005. f(*args)
  1006. return wrapped_f
  1007. return wrap
  1008. def get_topics():
  1009. topics = ['scheduler', 'conductor']
  1010. if 'nova-consoleauth' in services():
  1011. topics.append('consoleauth')
  1012. return topics
  1013. def cmd_all_services(cmd):
  1014. if is_unit_paused_set():
  1015. log('Unit is in paused state, not issuing {} to all'
  1016. 'services'.format(cmd))
  1017. return
  1018. if cmd == 'start':
  1019. for svc in services():
  1020. if not service_running(svc):
  1021. service_start(svc)
  1022. else:
  1023. for svc in services():
  1024. service(cmd, svc)
  1025. def disable_services():
  1026. for svc in services():
  1027. with open('/etc/init/{}.override'.format(svc), 'wb') as out:
  1028. out.write('exec true\n')
  1029. def enable_services():
  1030. for svc in services():
  1031. override_file = '/etc/init/{}.override'.format(svc)
  1032. if os.path.isfile(override_file):
  1033. os.remove(override_file)
  1034. def setup_ipv6():
  1035. ubuntu_rel = lsb_release()['DISTRIB_CODENAME'].lower()
  1036. if CompareHostReleases(ubuntu_rel) < "trusty":
  1037. raise Exception("IPv6 is not supported in the charms for Ubuntu "
  1038. "versions less than Trusty 14.04")
  1039. # Need haproxy >= 1.5.3 for ipv6 so for Trusty if we are <= Kilo we need to
  1040. # use trusty-backports otherwise we can use the UCA.
  1041. if (ubuntu_rel == 'trusty' and
  1042. CompareOpenStackReleases(os_release('nova-api')) < 'liberty'):
  1043. add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports '
  1044. 'main')
  1045. apt_update()
  1046. apt_install('haproxy/trusty-backports', fatal=True)
  1047. def git_install(projects_yaml):
  1048. """Perform setup, and install git repos specified in yaml parameter."""
  1049. if git_install_requested():
  1050. status_set('maintenance', 'Git install')
  1051. git_pre_install()
  1052. projects_yaml = git_default_repos(projects_yaml)
  1053. git_clone_and_install(projects_yaml, core_project='nova')
  1054. git_post_install(projects_yaml)
  1055. def git_pre_install():
  1056. """Perform pre-install setup."""
  1057. dirs = [
  1058. '/var/lib/nova',
  1059. '/var/lib/nova/buckets',
  1060. '/var/lib/nova/CA',
  1061. '/var/lib/nova/CA/INTER',
  1062. '/var/lib/nova/CA/newcerts',
  1063. '/var/lib/nova/CA/private',
  1064. '/var/lib/nova/CA/reqs',
  1065. '/var/lib/nova/images',
  1066. '/var/lib/nova/instances',
  1067. '/var/lib/nova/keys',
  1068. '/var/lib/nova/networks',
  1069. '/var/lib/nova/tmp',
  1070. '/var/lib/neutron',
  1071. '/var/lib/neutron/lock',
  1072. '/var/log/nova',
  1073. '/etc/neutron',
  1074. '/etc/neutron/plugins',
  1075. '/etc/neutron/plugins/ml2',
  1076. ]
  1077. adduser('nova', shell='/bin/bash', system_user=True)
  1078. subprocess.check_call(['usermod', '--home', '/var/lib/nova', 'nova'])
  1079. add_group('nova', system_group=True)
  1080. add_user_to_group('nova', 'nova')
  1081. adduser('neutron', shell='/bin/bash', system_user=True)
  1082. add_group('neutron', system_group=True)
  1083. add_user_to_group('neutron', 'neutron')
  1084. for d in dirs:
  1085. mkdir(d, owner='nova', group='nova', perms=0755, force=False)
  1086. def git_post_install(projects_yaml):
  1087. """Perform post-install setup."""
  1088. http_proxy = git_yaml_value(projects_yaml, 'http_proxy')
  1089. if http_proxy:
  1090. pip_install('mysql-python', proxy=http_proxy,
  1091. venv=git_pip_venv_dir(projects_yaml))
  1092. else:
  1093. pip_install('mysql-python',
  1094. venv=git_pip_venv_dir(projects_yaml))
  1095. src_etc = os.path.join(git_src_dir(projects_yaml, 'nova'), 'etc/nova')
  1096. configs = [
  1097. {'src': src_etc,
  1098. 'dest': '/etc/nova'},
  1099. ]
  1100. for c in configs:
  1101. if os.path.exists(c['dest']):
  1102. shutil.rmtree(c['dest'])
  1103. shutil.copytree(c['src'], c['dest'])
  1104. # NOTE(coreycb): Need to find better solution than bin symlinks.
  1105. symlinks = [
  1106. {'src': os.path.join(git_pip_venv_dir(projects_yaml),
  1107. 'bin/nova-manage'),
  1108. 'link': '/usr/local/bin/nova-manage'},
  1109. {'src': os.path.join(git_pip_venv_dir(projects_yaml),
  1110. 'bin/nova-rootwrap'),
  1111. 'link': '/usr/local/bin/nova-rootwrap'},
  1112. {'src': os.path.join(git_pip_venv_dir(projects_yaml),
  1113. 'bin/neutron-db-manage'),
  1114. 'link': '/usr/local/bin/neutron-db-manage'},
  1115. ]
  1116. for s in symlinks:
  1117. if os.path.lexists(s['link']):
  1118. os.remove(s['link'])
  1119. os.symlink(s['src'], s['link'])
  1120. render('git/nova_sudoers', '/etc/sudoers.d/nova_sudoers', {}, perms=0o440)
  1121. bin_dir = os.path.join(git_pip_venv_dir(projects_yaml), 'bin')
  1122. # Use systemd init units/scripts from ubuntu wily onward
  1123. if lsb_release()['DISTRIB_RELEASE'] >= '15.10':
  1124. templates_dir = os.path.join(charm_dir(), 'templates/git')
  1125. daemons = ['nova-api-os-compute', 'nova-baremetal-deploy-helper',
  1126. 'nova-cells', 'nova-cert', 'nova-conductor',
  1127. 'nova-consoleauth', 'nova-console', 'nova-novncproxy',
  1128. 'nova-scheduler', 'nova-serialproxy',
  1129. 'nova-spicehtml5proxy', 'nova-xvpvncproxy']
  1130. for daemon in daemons:
  1131. nova_compute_context = {
  1132. 'daemon_path': os.path.join(bin_dir, daemon),
  1133. }
  1134. if daemon == 'nova-baremetal-deploy-helper':
  1135. filename = 'nova-baremetal'
  1136. elif daemon == 'nova-spicehtml5proxy':
  1137. filename = 'nova-spiceproxy'
  1138. else:
  1139. filename = daemon
  1140. template_file = 'git/{}.init.in.template'.format(filename)
  1141. init_in_file = '{}.init.in'.format(filename)
  1142. render(template_file, os.path.join(templates_dir, init_in_file),
  1143. nova_compute_context, perms=0o644)
  1144. git_generate_systemd_init_files(templates_dir)
  1145. else:
  1146. nova_cc = 'nova-cloud-controller'
  1147. nova_user = 'nova'
  1148. start_dir = '/var/lib/nova'
  1149. nova_conf = '/etc/nova/nova.conf'
  1150. nova_ec2_api_context = {
  1151. 'service_description': 'Nova EC2 API server',
  1152. 'service_name': nova_cc,
  1153. 'user_name': nova_user,
  1154. 'start_dir': start_dir,
  1155. 'process_name': 'nova-api-ec2',
  1156. 'executable_name': os.path.join(bin_dir, 'nova-api-ec2'),
  1157. 'config_files': [nova_conf],
  1158. }
  1159. nova_api_os_compute_context = {
  1160. 'service_description': 'Nova OpenStack Compute API server',
  1161. 'service_name': nova_cc,
  1162. 'user_name': nova_user,
  1163. 'start_dir': start_dir,
  1164. 'process_name': 'nova-api-os-compute',
  1165. 'executable_name': os.path.join(bin_dir, 'nova-api-os-compute'),
  1166. 'config_files': [nova_conf],
  1167. }
  1168. nova_cells_context = {
  1169. 'service_description': 'Nova cells',
  1170. 'service_name': nova_cc,
  1171. 'user_name': nova_user,
  1172. 'start_dir': start_dir,
  1173. 'process_name': 'nova-cells',
  1174. 'executable_name': os.path.join(bin_dir, 'nova-cells'),
  1175. 'config_files': [nova_conf],
  1176. }
  1177. nova_cert_context = {
  1178. 'service_description': 'Nova cert',
  1179. 'service_name': nova_cc,
  1180. 'user_name': nova_user,
  1181. 'start_dir': start_dir,
  1182. 'process_name': 'nova-cert',
  1183. 'executable_name': os.path.join(bin_dir, 'nova-cert'),
  1184. 'config_files': [nova_conf],
  1185. }
  1186. nova_conductor_context = {
  1187. 'service_description': 'Nova conductor',
  1188. 'service_name': nova_cc,
  1189. 'user_name': nova_user,
  1190. 'start_dir': start_dir,
  1191. 'process_name': 'nova-conductor',
  1192. 'executable_name': os.path.join(bin_dir, 'nova-conductor'),
  1193. 'config_files': [nova_conf],
  1194. }
  1195. nova_consoleauth_context = {
  1196. 'service_description': 'Nova console auth',
  1197. 'service_name': nova_cc,
  1198. 'user_name': nova_user,
  1199. 'start_dir': start_dir,
  1200. 'process_name': 'nova-consoleauth',
  1201. 'executable_name': os.path.join(bin_dir, 'nova-consoleauth'),
  1202. 'config_files': [nova_conf],
  1203. }
  1204. nova_console_context = {
  1205. 'service_description': 'Nova console',
  1206. 'service_name': nova_cc,
  1207. 'user_name': nova_user,
  1208. 'start_dir': start_dir,
  1209. 'process_name': 'nova-console',
  1210. 'executable_name': os.path.join(bin_dir, 'nova-console'),
  1211. 'config_files': [nova_conf],
  1212. }
  1213. nova_novncproxy_context = {
  1214. 'service_description': 'Nova NoVNC proxy',
  1215. 'service_name': nova_cc,
  1216. 'user_name': nova_user,
  1217. 'start_dir': start_dir,
  1218. 'process_name': 'nova-novncproxy',
  1219. 'executable_name': os.path.join(bin_dir, 'nova-novncproxy'),
  1220. 'config_files': [nova_conf],
  1221. }
  1222. nova_objectstore_context = {
  1223. 'service_description': 'Nova object store',
  1224. 'service_name': nova_cc,
  1225. 'user_name': nova_user,
  1226. 'start_dir': start_dir,
  1227. 'process_name': 'nova-objectstore',
  1228. 'executable_name': os.path.join(bin_dir, 'nova-objectstore'),
  1229. 'config_files': [nova_conf],
  1230. }
  1231. nova_scheduler_context = {
  1232. 'service_description': 'Nova scheduler',
  1233. 'service_name': nova_cc,
  1234. 'user_name': nova_user,
  1235. 'start_dir': start_dir,
  1236. 'process_name': 'nova-scheduler',
  1237. 'executable_name': os.path.join(bin_dir, 'nova-scheduler'),
  1238. 'config_files': [nova_conf],
  1239. }
  1240. nova_serialproxy_context = {
  1241. 'service_description': 'Nova serial proxy',
  1242. 'service_name': nova_cc,
  1243. 'user_name': nova_user,
  1244. 'start_dir': start_dir,
  1245. 'process_name': 'nova-serialproxy',
  1246. 'executable_name': os.path.join(bin_dir, 'nova-serialproxy'),
  1247. 'config_files': [nova_conf],
  1248. }
  1249. nova_spiceproxy_context = {
  1250. 'service_description': 'Nova spice proxy',
  1251. 'service_name': nova_cc,
  1252. 'user_name': nova_user,
  1253. 'start_dir': start_dir,
  1254. 'process_name': 'nova-spicehtml5proxy',
  1255. 'executable_name': os.path.join(bin_dir, 'nova-spicehtml5proxy'),
  1256. 'config_files': [nova_conf],
  1257. }
  1258. nova_xvpvncproxy_context = {
  1259. 'service_description': 'Nova XVPVNC proxy',
  1260. 'service_name': nova_cc,
  1261. 'user_name': nova_user,
  1262. 'start_dir': start_dir,
  1263. 'process_name': 'nova-xvpvncproxy',
  1264. 'executable_name': os.path.join(bin_dir, 'nova-xvpvncproxy'),
  1265. 'config_files': [nova_conf],
  1266. }
  1267. templates_dir = 'hooks/charmhelpers/contrib/openstack/templates'
  1268. templates_dir = os.path.join(charm_dir(), templates_dir)
  1269. os_rel = os_release('nova-common')
  1270. render('git.upstart', '/etc/init/nova-api-ec2.conf',
  1271. nova_ec2_api_context, perms=0o644,
  1272. templates_dir=templates_dir)
  1273. render('git.upstart', '/etc/init/nova-api-os-compute.conf',
  1274. nova_api_os_compute_context, perms=0o644,
  1275. templates_dir=templates_dir)
  1276. render('git.upstart', '/etc/init/nova-cells.conf',
  1277. nova_cells_context, perms=0o644,
  1278. templates_dir=templates_dir)
  1279. render('git.upstart', '/etc/init/nova-cert.conf',
  1280. nova_cert_context, perms=0o644,
  1281. templates_dir=templates_dir)
  1282. render('git.upstart', '/etc/init/nova-conductor.conf',
  1283. nova_conductor_context, perms=0o644,
  1284. templates_dir=templates_dir)
  1285. render('git.upstart', '/etc/init/nova-consoleauth.conf',
  1286. nova_consoleauth_context, perms=0o644,
  1287. templates_dir=templates_dir)
  1288. render('git.upstart', '/etc/init/nova-console.conf',
  1289. nova_console_context, perms=0o644,
  1290. templates_dir=templates_dir)
  1291. render('git.upstart', '/etc/init/nova-novncproxy.conf',
  1292. nova_novncproxy_context, perms=0o644,
  1293. templates_dir=templates_dir)
  1294. render('git.upstart', '/etc/init/nova-objectstore.conf',
  1295. nova_objectstore_context, perms=0o644,
  1296. templates_dir=templates_dir)
  1297. render('git.upstart', '/etc/init/nova-scheduler.conf',
  1298. nova_scheduler_context, perms=0o644,
  1299. templates_dir=templates_dir)
  1300. if CompareOpenStackReleases(os_rel) >= 'juno':
  1301. render('git.upstart', '/etc/init/nova-serialproxy.conf',
  1302. nova_serialproxy_context, perms=0o644,
  1303. templates_dir=templates_dir)
  1304. render('git.upstart', '/etc/init/nova-spiceproxy.conf',
  1305. nova_spiceproxy_context, perms=0o644,
  1306. templates_dir=templates_dir)
  1307. render('git.upstart', '/etc/init/nova-xvpvncproxy.conf',
  1308. nova_xvpvncproxy_context, perms=0o644,
  1309. templates_dir=templates_dir)
  1310. apt_update()
  1311. apt_install(LATE_GIT_PACKAGES, fatal=True)
  1312. def get_optional_interfaces():
  1313. """Return the optional interfaces that should be checked if the relavent
  1314. relations have appeared.
  1315. :returns: {general_interface: [specific_int1, specific_int2, ...], ...}
  1316. """
  1317. optional_interfaces = {}
  1318. if relation_ids('quantum-network-service'):
  1319. optional_interfaces['quantum'] = ['quantum-network-service']
  1320. if relation_ids('cinder-volume-service'):
  1321. optional_interfaces['cinder'] = ['cinder-volume-service']
  1322. if relation_ids('neutron-api'):
  1323. optional_interfaces['neutron-api'] = ['neutron-api']
  1324. return optional_interfaces
  1325. def check_optional_relations(configs):
  1326. """Check that if we have a relation_id for high availability that we can
  1327. get the hacluster config. If we can't then we are blocked.
  1328. This function is called from assess_status/set_os_workload_status as the
  1329. charm_func and needs to return either None, None if there is no problem or
  1330. the status, message if there is a problem.
  1331. :param configs: an OSConfigRender() instance.
  1332. :return 2-tuple: (string, string) = (status, message)
  1333. """
  1334. if relation_ids('ha'):
  1335. try:
  1336. get_hacluster_config()
  1337. except:
  1338. return ('blocked',
  1339. 'hacluster missing configuration: '
  1340. 'vip, vip_iface, vip_cidr')
  1341. # return 'unknown' as the lowest priority to not clobber an existing
  1342. # status.
  1343. return "unknown", ""
  1344. def is_api_ready(configs):
  1345. return (not incomplete_relation_data(configs, REQUIRED_INTERFACES))
  1346. def assess_status(configs):
  1347. """Assess status of current unit
  1348. Decides what the state of the unit should be based on the current
  1349. configuration.
  1350. SIDE EFFECT: calls set_os_workload_status(...) which sets the workload
  1351. status of the unit.
  1352. Also calls status_set(...) directly if paused state isn't complete.
  1353. @param configs: a templating.OSConfigRenderer() object
  1354. @returns None - this function is executed for its side-effect
  1355. """
  1356. assess_status_func(configs)()
  1357. os_application_version_set(VERSION_PACKAGE)
  1358. def assess_status_func(configs):
  1359. """Helper function to create the function that will assess_status() for
  1360. the unit.
  1361. Uses charmhelpers.contrib.openstack.utils.make_assess_status_func() to
  1362. create the appropriate status function and then returns it.
  1363. Used directly by assess_status() and also for pausing and resuming
  1364. the unit.
  1365. NOTE: REQUIRED_INTERFACES is augmented with the optional interfaces
  1366. depending on the current config before being passed to the
  1367. make_assess_status_func() function.
  1368. NOTE(ajkavanagh) ports are not checked due to race hazards with services
  1369. that don't behave sychronously w.r.t their service scripts. e.g.
  1370. apache2.
  1371. @param configs: a templating.OSConfigRenderer() object
  1372. @return f() -> None : a function that assesses the unit's workload status
  1373. """
  1374. required_interfaces = REQUIRED_INTERFACES.copy()
  1375. required_interfaces.update(get_optional_interfaces())
  1376. return make_assess_status_func(
  1377. configs, required_interfaces,
  1378. charm_func=check_optional_relations,
  1379. services=services(), ports=None)
  1380. def pause_unit_helper(configs):
  1381. """Helper function to pause a unit, and then call assess_status(...) in
  1382. effect, so that the status is correctly updated.
  1383. Uses charmhelpers.contrib.openstack.utils.pause_unit() to do the work.
  1384. @param configs: a templating.OSConfigRenderer() object
  1385. @returns None - this function is executed for its side-effect
  1386. """
  1387. _pause_resume_helper(pause_unit, configs)
  1388. def resume_unit_helper(configs):
  1389. """Helper function to resume a unit, and then call assess_status(...) in
  1390. effect, so that the status is correctly updated.
  1391. Uses charmhelpers.contrib.openstack.utils.resume_unit() to do the work.
  1392. @param configs: a templating.OSConfigRenderer() object
  1393. @returns None - this function is executed for its side-effect
  1394. """
  1395. _pause_resume_helper(resume_unit, configs)
  1396. def _pause_resume_helper(f, configs):
  1397. """Helper function that uses the make_assess_status_func(...) from
  1398. charmhelpers.contrib.openstack.utils to create an assess_status(...)
  1399. function that can be used with the pause/resume of the unit
  1400. @param f: the function to be used with the assess_status(...) function
  1401. @returns None - this function is executed for its side-effect
  1402. """
  1403. # TODO(ajkavanagh) - ports= has been left off because of the race hazard
  1404. # that exists due to service_start()
  1405. f(assess_status_func(configs),
  1406. services=services(),
  1407. ports=None)
  1408. def update_aws_compat_services():
  1409. """Depending on the configuration of `disable-aws-compatibility` config
  1410. option.
  1411. This will stop/start and disable/enable `nova-api-ec2` and
  1412. `nova-objectstore` services.
  1413. """
  1414. # if packages aren't installed, then there is nothing to do
  1415. if filter_installed_packages(AWS_COMPAT_SERVICES) != []:
  1416. return
  1417. if config('disable-aws-compat'):
  1418. # TODO: the endpoints have to removed from keystone
  1419. for service_ in AWS_COMPAT_SERVICES:
  1420. service_pause(service_)
  1421. else:
  1422. for service_ in AWS_COMPAT_SERVICES:
  1423. service_resume(service_)
  1424. def serial_console_settings():
  1425. '''Utility wrapper to retrieve serial console settings
  1426. for use in cloud-compute relation
  1427. '''
  1428. return nova_cc_context.SerialConsoleContext()()
  1429. def placement_api_enabled():
  1430. """Return true if nova-placement-api is enabled in this release"""
  1431. return CompareOpenStackReleases(os_release('nova-common')) >= 'ocata'
  1432. def disable_package_apache_site():
  1433. """Ensure that the package-provided apache configuration is disabled to
  1434. prevent it from conflicting with the charm-provided version.
  1435. """
  1436. if os.path.exists(PACKAGE_NOVA_PLACEMENT_API_CONF):
  1437. subprocess.check_call(['a2dissite', 'nova-placement-api'])