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.

basic_deployment.py 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  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 amulet
  15. from charmhelpers.contrib.openstack.amulet.deployment import (
  16. OpenStackAmuletDeployment
  17. )
  18. from charmhelpers.contrib.openstack.amulet.utils import (
  19. OpenStackAmuletUtils,
  20. DEBUG,
  21. # ERROR
  22. )
  23. from charmhelpers.contrib.openstack.utils import CompareOpenStackReleases
  24. from novaclient import exceptions
  25. class NovaOpenStackAmuletUtils(OpenStackAmuletUtils):
  26. """Nova based helper extending base helper for creation of flavors"""
  27. def create_flavor(self, nova, name, ram, vcpus, disk, flavorid="auto",
  28. ephemeral=0, swap=0, rxtx_factor=1.0, is_public=True):
  29. """Create the specified flavor."""
  30. try:
  31. nova.flavors.find(name=name)
  32. except (exceptions.NotFound, exceptions.NoUniqueMatch):
  33. self.log.debug('Creating flavor ({})'.format(name))
  34. nova.flavors.create(name, ram, vcpus, disk, flavorid,
  35. ephemeral, swap, rxtx_factor, is_public)
  36. # Use DEBUG to turn on debug logging
  37. u = NovaOpenStackAmuletUtils(DEBUG)
  38. class NovaCCBasicDeployment(OpenStackAmuletDeployment):
  39. """Amulet tests on a basic nova cloud controller deployment."""
  40. def __init__(self, series=None, openstack=None, source=None,
  41. stable=False):
  42. """Deploy the entire test environment."""
  43. super(NovaCCBasicDeployment, self).__init__(series, openstack,
  44. source, stable)
  45. self._add_services()
  46. self._add_relations()
  47. self._configure_services()
  48. self._deploy()
  49. u.log.info('Waiting on extended status checks...')
  50. exclude_services = []
  51. self._auto_wait_for_status(exclude_services=exclude_services)
  52. self.d.sentry.wait()
  53. self._initialize_tests()
  54. def _assert_services(self, should_run):
  55. services = ["nova-api-os-compute", "nova-cert", "nova-conductor",
  56. "nova-scheduler", "apache2", "haproxy"]
  57. cmp_os_release = CompareOpenStackReleases(
  58. self._get_openstack_release_string()
  59. )
  60. if cmp_os_release >= 'newton':
  61. services.remove('nova-cert')
  62. u.get_unit_process_ids(
  63. {self.nova_cc_sentry: services},
  64. expect_success=should_run)
  65. def _add_services(self):
  66. """Add services
  67. Add the services that we're testing, where nova-cc is local,
  68. and the rest of the service are from lp branches that are
  69. compatible with the local charm (e.g. stable or next).
  70. """
  71. this_service = {'name': 'nova-cloud-controller'}
  72. other_services = [
  73. {'name': 'rabbitmq-server'},
  74. {'name': 'nova-compute', 'units': 2},
  75. {'name': 'keystone'},
  76. {'name': 'glance'},
  77. {'name': 'percona-cluster', 'constraints': {'mem': '3072M'}},
  78. ]
  79. if self._get_openstack_release() >= self.xenial_ocata:
  80. other_ocata_services = [
  81. {'name': 'neutron-gateway'},
  82. {'name': 'neutron-api'},
  83. {'name': 'neutron-openvswitch'},
  84. ]
  85. other_services += other_ocata_services
  86. super(NovaCCBasicDeployment, self)._add_services(this_service,
  87. other_services)
  88. def _add_relations(self):
  89. """Add all of the relations for the services."""
  90. relations = {
  91. 'nova-cloud-controller:shared-db': 'percona-cluster:shared-db',
  92. 'nova-cloud-controller:identity-service': 'keystone:'
  93. 'identity-service',
  94. 'nova-cloud-controller:amqp': 'rabbitmq-server:amqp',
  95. 'nova-cloud-controller:cloud-compute': 'nova-compute:'
  96. 'cloud-compute',
  97. 'nova-cloud-controller:image-service': 'glance:image-service',
  98. 'nova-compute:image-service': 'glance:image-service',
  99. 'nova-compute:shared-db': 'percona-cluster:shared-db',
  100. 'nova-compute:amqp': 'rabbitmq-server:amqp',
  101. 'keystone:shared-db': 'percona-cluster:shared-db',
  102. 'glance:identity-service': 'keystone:identity-service',
  103. 'glance:shared-db': 'percona-cluster:shared-db',
  104. 'glance:amqp': 'rabbitmq-server:amqp',
  105. }
  106. if self._get_openstack_release() >= self.xenial_ocata:
  107. ocata_relations = {
  108. 'neutron-gateway:amqp': 'rabbitmq-server:amqp',
  109. 'nova-cloud-controller:quantum-network-service':
  110. 'neutron-gateway:quantum-network-service',
  111. 'neutron-api:shared-db': 'percona-cluster:shared-db',
  112. 'neutron-api:amqp': 'rabbitmq-server:amqp',
  113. 'neutron-api:neutron-api': 'nova-cloud-controller:neutron-api',
  114. 'neutron-api:identity-service': 'keystone:identity-service',
  115. 'nova-compute:neutron-plugin': 'neutron-openvswitch:'
  116. 'neutron-plugin',
  117. 'rabbitmq-server:amqp': 'neutron-openvswitch:amqp',
  118. }
  119. relations.update(ocata_relations)
  120. super(NovaCCBasicDeployment, self)._add_relations(relations)
  121. def _configure_services(self):
  122. """Configure all of the services."""
  123. nova_cc_config = {}
  124. nova_config = {}
  125. # Add some rate-limiting options to the charm. These will noop before
  126. # icehouse.
  127. nova_cc_config['api-rate-limit-rules'] = \
  128. "( POST, '*', .*, 9999, MINUTE );"
  129. if self._get_openstack_release() >= self.xenial_ocata:
  130. nova_cc_config['network-manager'] = 'Neutron'
  131. keystone_config = {'admin-password': 'openstack',
  132. 'admin-token': 'ubuntutesting'}
  133. pxc_config = {
  134. 'dataset-size': '25%',
  135. 'max-connections': 1000,
  136. 'root-password': 'ChangeMe123',
  137. 'sst-password': 'ChangeMe123',
  138. }
  139. configs = {
  140. 'nova-cloud-controller': nova_cc_config,
  141. 'keystone': keystone_config,
  142. 'nova-compute': nova_config,
  143. 'percona-cluster': pxc_config,
  144. }
  145. super(NovaCCBasicDeployment, self)._configure_services(configs)
  146. def _initialize_tests(self):
  147. """Perform final initialization before tests get run."""
  148. # Access the sentries for inspecting service units
  149. self.pxc_sentry = self.d.sentry['percona-cluster'][0]
  150. self.keystone_sentry = self.d.sentry['keystone'][0]
  151. self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0]
  152. self.nova_cc_sentry = self.d.sentry['nova-cloud-controller'][0]
  153. self.nova_compute_sentry = self.d.sentry['nova-compute'][0]
  154. self.glance_sentry = self.d.sentry['glance'][0]
  155. u.log.debug('openstack release val: {}'.format(
  156. self._get_openstack_release()))
  157. u.log.debug('openstack release str: {}'.format(
  158. self._get_openstack_release_string()))
  159. # Authenticate admin with keystone
  160. self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
  161. user='admin',
  162. password='openstack',
  163. tenant='admin')
  164. # Authenticate admin with glance endpoint
  165. self.glance = u.authenticate_glance_admin(self.keystone)
  166. # Authenticate admin with nova endpoint
  167. self.nova = u.authenticate_nova_user(self.keystone,
  168. user='admin',
  169. password='openstack',
  170. tenant='admin')
  171. # Create a demo tenant/role/user
  172. self.demo_tenant = 'demoTenant'
  173. self.demo_role = 'demoRole'
  174. self.demo_user = 'demoUser'
  175. if not u.tenant_exists(self.keystone, self.demo_tenant):
  176. tenant = self.keystone.tenants.create(tenant_name=self.demo_tenant,
  177. description='demo tenant',
  178. enabled=True)
  179. self.keystone.roles.create(name=self.demo_role)
  180. self.keystone.users.create(name=self.demo_user,
  181. password='password',
  182. tenant_id=tenant.id,
  183. email='demo@demo.com')
  184. # Authenticate demo user with keystone
  185. self.keystone_demo = \
  186. u.authenticate_keystone_user(self.keystone, user=self.demo_user,
  187. password='password',
  188. tenant=self.demo_tenant)
  189. # Authenticate demo user with nova-api
  190. self.nova_demo = u.authenticate_nova_user(self.keystone,
  191. user=self.demo_user,
  192. password='password',
  193. tenant=self.demo_tenant)
  194. def test_100_services(self):
  195. """Verify the expected services are running on the corresponding
  196. service units."""
  197. u.log.debug('Checking system services on units...')
  198. services = {
  199. self.rabbitmq_sentry: ['rabbitmq-server'],
  200. self.nova_cc_sentry: ['nova-api-ec2',
  201. 'nova-api-os-compute',
  202. 'nova-conductor',
  203. 'nova-objectstore',
  204. 'nova-cert',
  205. 'nova-scheduler'],
  206. self.nova_compute_sentry: ['nova-compute',
  207. 'nova-network',
  208. 'nova-api'],
  209. self.keystone_sentry: ['keystone'],
  210. self.glance_sentry: ['glance-registry', 'glance-api']
  211. }
  212. cmp_os_release = CompareOpenStackReleases(
  213. self._get_openstack_release_string()
  214. )
  215. if cmp_os_release >= 'liberty':
  216. services[self.nova_cc_sentry].remove('nova-api-ec2')
  217. services[self.nova_cc_sentry].remove('nova-objectstore')
  218. if cmp_os_release >= 'newton':
  219. services[self.nova_cc_sentry].remove('nova-cert')
  220. if self._get_openstack_release() >= self.trusty_liberty:
  221. services[self.keystone_sentry] = ['apache2']
  222. if self._get_openstack_release() >= self.xenial_ocata:
  223. services[self.nova_compute_sentry].remove('nova-network')
  224. services[self.nova_compute_sentry].remove('nova-api')
  225. ret = u.validate_services_by_name(services)
  226. if ret:
  227. amulet.raise_status(amulet.FAIL, msg=ret)
  228. def test_102_service_catalog(self):
  229. """Verify that the service catalog endpoint data is valid."""
  230. u.log.debug('Checking keystone service catalog...')
  231. endpoint_vol = {'adminURL': u.valid_url,
  232. 'region': 'RegionOne',
  233. 'id': u.not_null,
  234. 'publicURL': u.valid_url,
  235. 'internalURL': u.valid_url}
  236. endpoint_id = {'adminURL': u.valid_url,
  237. 'region': 'RegionOne',
  238. 'id': u.not_null,
  239. 'publicURL': u.valid_url,
  240. 'internalURL': u.valid_url}
  241. if self._get_openstack_release() >= self.trusty_kilo:
  242. expected = {'compute': [endpoint_vol], 'identity': [endpoint_id]}
  243. else:
  244. expected = {'s3': [endpoint_vol], 'compute': [endpoint_vol],
  245. 'ec2': [endpoint_vol], 'identity': [endpoint_id]}
  246. actual = self.keystone_demo.service_catalog.get_endpoints()
  247. ret = u.validate_svc_catalog_endpoint_data(expected, actual)
  248. if ret:
  249. amulet.raise_status(amulet.FAIL, msg=ret)
  250. def test_104_openstack_compute_api_endpoint(self):
  251. """Verify the openstack compute api (osapi) endpoint data."""
  252. u.log.debug('Checking compute endpoint data...')
  253. endpoints = self.keystone.endpoints.list()
  254. admin_port = internal_port = public_port = '8774'
  255. expected = {
  256. 'id': u.not_null,
  257. 'region': 'RegionOne',
  258. 'adminurl': u.valid_url,
  259. 'internalurl': u.valid_url,
  260. 'publicurl': u.valid_url,
  261. 'service_id': u.not_null
  262. }
  263. ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
  264. public_port, expected)
  265. if ret:
  266. message = 'osapi endpoint: {}'.format(ret)
  267. amulet.raise_status(amulet.FAIL, msg=message)
  268. def test_106_ec2_api_endpoint(self):
  269. """Verify the EC2 api endpoint data."""
  270. if self._get_openstack_release() >= self.trusty_kilo:
  271. return
  272. u.log.debug('Checking ec2 endpoint data...')
  273. endpoints = self.keystone.endpoints.list()
  274. admin_port = internal_port = public_port = '8773'
  275. expected = {
  276. 'id': u.not_null,
  277. 'region': 'RegionOne',
  278. 'adminurl': u.valid_url,
  279. 'internalurl': u.valid_url,
  280. 'publicurl': u.valid_url,
  281. 'service_id': u.not_null
  282. }
  283. ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
  284. public_port, expected)
  285. if ret:
  286. message = 'EC2 endpoint: {}'.format(ret)
  287. amulet.raise_status(amulet.FAIL, msg=message)
  288. def test_108_s3_api_endpoint(self):
  289. """Verify the S3 api endpoint data."""
  290. if self._get_openstack_release() >= self.trusty_kilo:
  291. return
  292. u.log.debug('Checking s3 endpoint data...')
  293. endpoints = self.keystone.endpoints.list()
  294. admin_port = internal_port = public_port = '3333'
  295. expected = {
  296. 'id': u.not_null,
  297. 'region': 'RegionOne',
  298. 'adminurl': u.valid_url,
  299. 'internalurl': u.valid_url,
  300. 'publicurl': u.valid_url,
  301. 'service_id': u.not_null
  302. }
  303. ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
  304. public_port, expected)
  305. if ret:
  306. message = 'S3 endpoint: {}'.format(ret)
  307. amulet.raise_status(amulet.FAIL, msg=message)
  308. def test_110_memcache(self):
  309. u.validate_memcache(self.nova_cc_sentry,
  310. '/etc/nova/nova.conf',
  311. self._get_openstack_release(),
  312. earliest_release=self.trusty_mitaka)
  313. def test_200_nova_cc_shared_db_relation(self):
  314. """Verify the nova-cc to mysql shared-db relation data"""
  315. u.log.debug('Checking n-c-c:mysql db relation data...')
  316. unit = self.nova_cc_sentry
  317. relation = ['shared-db', 'percona-cluster:shared-db']
  318. expected = {
  319. 'private-address': u.valid_ip,
  320. 'nova_database': 'nova',
  321. 'nova_username': 'nova',
  322. 'nova_hostname': u.valid_ip
  323. }
  324. ret = u.validate_relation_data(unit, relation, expected)
  325. if ret:
  326. message = u.relation_error('nova-cc shared-db', ret)
  327. amulet.raise_status(amulet.FAIL, msg=message)
  328. def test_202_mysql_shared_db_relation(self):
  329. """Verify the mysql to nova-cc shared-db relation data"""
  330. u.log.debug('Checking mysql:n-c-c db relation data...')
  331. unit = self.pxc_sentry
  332. relation = ['shared-db', 'nova-cloud-controller:shared-db']
  333. expected = {
  334. 'private-address': u.valid_ip,
  335. 'nova_password': u.not_null,
  336. 'db_host': u.valid_ip
  337. }
  338. ret = u.validate_relation_data(unit, relation, expected)
  339. if ret:
  340. message = u.relation_error('mysql shared-db', ret)
  341. amulet.raise_status(amulet.FAIL, msg=message)
  342. def test_204_nova_cc_identity_service_relation(self):
  343. """Verify the nova-cc to keystone identity-service relation data"""
  344. u.log.debug('Checking n-c-c:keystone identity relation data...')
  345. unit = self.nova_cc_sentry
  346. relation = ['identity-service', 'keystone:identity-service']
  347. expected = {
  348. 'nova_internal_url': u.valid_url,
  349. 'nova_public_url': u.valid_url,
  350. 'nova_service': 'nova',
  351. 'private-address': u.valid_ip,
  352. 'nova_region': 'RegionOne',
  353. 'nova_admin_url': u.valid_url,
  354. }
  355. if self._get_openstack_release() < self.trusty_kilo:
  356. expected['s3_admin_url'] = u.valid_url
  357. expected['s3_internal_url'] = u.valid_url
  358. expected['s3_public_url'] = u.valid_url
  359. expected['s3_region'] = 'RegionOne'
  360. expected['s3_service'] = 's3'
  361. expected['ec2_admin_url'] = u.valid_url
  362. expected['ec2_internal_url'] = u.valid_url
  363. expected['ec2_public_url'] = u.valid_url
  364. expected['ec2_region'] = 'RegionOne'
  365. expected['ec2_service'] = 'ec2'
  366. if self._get_openstack_release() >= self.xenial_ocata:
  367. expected['placement_service'] = 'placement'
  368. expected['placement_internal_url'] = u.valid_url
  369. expected['placement_public_url'] = u.valid_url
  370. expected['placement_admin_url'] = u.valid_url
  371. expected['placement_region'] = 'RegionOne'
  372. ret = u.validate_relation_data(unit, relation, expected)
  373. if ret:
  374. message = u.relation_error('nova-cc identity-service', ret)
  375. amulet.raise_status(amulet.FAIL, msg=message)
  376. def test_206_keystone_identity_service_relation(self):
  377. """Verify the keystone to nova-cc identity-service relation data"""
  378. u.log.debug('Checking keystone:n-c-c identity relation data...')
  379. unit = self.keystone_sentry
  380. relation = ['identity-service',
  381. 'nova-cloud-controller:identity-service']
  382. expected = {
  383. 'service_protocol': 'http',
  384. 'service_tenant': 'services',
  385. 'admin_token': 'ubuntutesting',
  386. 'service_password': u.not_null,
  387. 'service_port': '5000',
  388. 'auth_port': '35357',
  389. 'auth_protocol': 'http',
  390. 'private-address': u.valid_ip,
  391. 'auth_host': u.valid_ip,
  392. 'service_username': 'ec2_nova_s3',
  393. 'service_tenant_id': u.not_null,
  394. 'service_host': u.valid_ip
  395. }
  396. if self._get_openstack_release() >= self.trusty_kilo:
  397. expected['service_username'] = 'nova'
  398. if self._get_openstack_release() >= self.xenial_ocata:
  399. expected['service_username'] = 'nova_placement'
  400. ret = u.validate_relation_data(unit, relation, expected)
  401. if ret:
  402. message = u.relation_error('keystone identity-service', ret)
  403. amulet.raise_status(amulet.FAIL, msg=message)
  404. def test_208_nova_cc_amqp_relation(self):
  405. """Verify the nova-cc to rabbitmq-server amqp relation data"""
  406. u.log.debug('Checking n-c-c:rmq amqp relation data...')
  407. unit = self.nova_cc_sentry
  408. relation = ['amqp', 'rabbitmq-server:amqp']
  409. expected = {
  410. 'username': 'nova',
  411. 'private-address': u.valid_ip,
  412. 'vhost': 'openstack'
  413. }
  414. ret = u.validate_relation_data(unit, relation, expected)
  415. if ret:
  416. message = u.relation_error('nova-cc amqp', ret)
  417. amulet.raise_status(amulet.FAIL, msg=message)
  418. def test_210_rabbitmq_amqp_relation(self):
  419. """Verify the rabbitmq-server to nova-cc amqp relation data"""
  420. u.log.debug('Checking rmq:n-c-c amqp relation data...')
  421. unit = self.rabbitmq_sentry
  422. relation = ['amqp', 'nova-cloud-controller:amqp']
  423. expected = {
  424. 'private-address': u.valid_ip,
  425. 'password': u.not_null,
  426. 'hostname': u.valid_ip
  427. }
  428. ret = u.validate_relation_data(unit, relation, expected)
  429. if ret:
  430. message = u.relation_error('rabbitmq amqp', ret)
  431. amulet.raise_status(amulet.FAIL, msg=message)
  432. def test_212_nova_cc_cloud_compute_relation(self):
  433. """Verify the nova-cc to nova-compute cloud-compute relation data"""
  434. u.log.debug('Checking n-c-c:nova-compute '
  435. 'cloud-compute relation data...')
  436. unit = self.nova_cc_sentry
  437. relation = ['cloud-compute', 'nova-compute:cloud-compute']
  438. expected = {
  439. 'volume_service': 'cinder',
  440. 'network_manager': 'flatdhcpmanager',
  441. 'ec2_host': u.valid_ip,
  442. 'private-address': u.valid_ip,
  443. 'restart_trigger': u.not_null
  444. }
  445. if self._get_openstack_release() >= self.xenial_ocata:
  446. expected['network_manager'] = 'neutron'
  447. ret = u.validate_relation_data(unit, relation, expected)
  448. if ret:
  449. message = u.relation_error('nova-cc cloud-compute', ret)
  450. amulet.raise_status(amulet.FAIL, msg=message)
  451. def test_214_nova_cloud_compute_relation(self):
  452. """Verify the nova-compute to nova-cc cloud-compute relation data"""
  453. u.log.debug('Checking nova-compute:n-c-c '
  454. 'cloud-compute relation data...')
  455. unit = self.nova_compute_sentry
  456. relation = ['cloud-compute', 'nova-cloud-controller:cloud-compute']
  457. expected = {
  458. 'private-address': u.valid_ip,
  459. }
  460. ret = u.validate_relation_data(unit, relation, expected)
  461. if ret:
  462. message = u.relation_error('nova-compute cloud-compute', ret)
  463. amulet.raise_status(amulet.FAIL, msg=message)
  464. def test_216_nova_cc_image_service_relation(self):
  465. """Verify the nova-cc to glance image-service relation data"""
  466. u.log.debug('Checking n-c-c:glance image-service relation data...')
  467. unit = self.nova_cc_sentry
  468. relation = ['image-service', 'glance:image-service']
  469. expected = {
  470. 'private-address': u.valid_ip,
  471. }
  472. ret = u.validate_relation_data(unit, relation, expected)
  473. if ret:
  474. message = u.relation_error('nova-cc image-service', ret)
  475. amulet.raise_status(amulet.FAIL, msg=message)
  476. def test_218_glance_image_service_relation(self):
  477. """Verify the glance to nova-cc image-service relation data"""
  478. u.log.debug('Checking glance:n-c-c image-service relation data...')
  479. unit = self.glance_sentry
  480. relation = ['image-service', 'nova-cloud-controller:image-service']
  481. expected = {
  482. 'private-address': u.valid_ip,
  483. 'glance-api-server': u.valid_url
  484. }
  485. ret = u.validate_relation_data(unit, relation, expected)
  486. if ret:
  487. message = u.relation_error('glance image-service', ret)
  488. amulet.raise_status(amulet.FAIL, msg=message)
  489. def test_300_nova_default_config(self):
  490. """Verify the data in the nova config file's default section."""
  491. u.log.debug('Checking nova config file data...')
  492. unit = self.nova_cc_sentry
  493. conf = '/etc/nova/nova.conf'
  494. rmq_ncc_rel = self.rabbitmq_sentry.relation(
  495. 'amqp', 'nova-cloud-controller:amqp')
  496. gl_ncc_rel = self.glance_sentry.relation(
  497. 'image-service', 'nova-cloud-controller:image-service')
  498. # Since >= liberty endpoint_type was replaced by interface
  499. # https://github.com/openstack/keystoneauth/commit/d227f6d237c4309b21a32a115fc5b09b9ba46ef0
  500. try:
  501. ks_ep = self.keystone_demo.service_catalog.url_for(
  502. service_type='identity', interface='publicURL')
  503. except TypeError:
  504. ks_ep = self.keystone_demo.service_catalog.url_for(
  505. service_type='identity', endpoint_type='publicURL')
  506. ks_ec2 = "{}/ec2tokens".format(ks_ep)
  507. ks_ncc_rel = self.keystone_sentry.relation(
  508. 'identity-service', 'nova-cloud-controller:identity-service')
  509. ks_uri = "http://{}:{}/".format(ks_ncc_rel['service_host'],
  510. ks_ncc_rel['service_port'])
  511. id_uri = "{}://{}:{}/".format(ks_ncc_rel['auth_protocol'],
  512. ks_ncc_rel['service_host'],
  513. ks_ncc_rel['auth_port'])
  514. db_ncc_rel = self.pxc_sentry.relation(
  515. 'shared-db', 'nova-cloud-controller:shared-db')
  516. db_uri = "mysql://{}:{}@{}/{}".format('nova',
  517. db_ncc_rel['nova_password'],
  518. db_ncc_rel['db_host'],
  519. 'nova')
  520. expected = {
  521. 'DEFAULT': {
  522. 'dhcpbridge_flagfile': '/etc/nova/nova.conf',
  523. 'dhcpbridge': '/usr/bin/nova-dhcpbridge',
  524. 'logdir': '/var/log/nova',
  525. 'state_path': '/var/lib/nova',
  526. 'force_dhcp_release': 'True',
  527. 'iscsi_helper': 'tgtadm',
  528. 'libvirt_use_virtio_for_bridges': 'True',
  529. 'connection_type': 'libvirt',
  530. 'root_helper': 'sudo nova-rootwrap /etc/nova/rootwrap.conf',
  531. 'verbose': 'False',
  532. 'debug': 'False',
  533. 'api_paste_config': '/etc/nova/api-paste.ini',
  534. 'volumes_path': '/var/lib/nova/volumes',
  535. 'auth_strategy': 'keystone',
  536. 'compute_driver': 'libvirt.LibvirtDriver',
  537. 'network_manager': 'nova.network.manager.FlatDHCPManager',
  538. 's3_listen_port': '3323',
  539. 'osapi_compute_listen_port': '8764',
  540. }
  541. }
  542. if self._get_openstack_release() < self.trusty_kilo:
  543. # Juno and earlier
  544. expected['database'] = {
  545. 'connection': db_uri
  546. }
  547. expected['keystone_authtoken'] = {
  548. 'auth_uri': ks_uri,
  549. 'auth_host': ks_ncc_rel['service_host'],
  550. 'auth_port': ks_ncc_rel['auth_port'],
  551. 'auth_protocol': ks_ncc_rel['auth_protocol'],
  552. 'admin_tenant_name': ks_ncc_rel['service_tenant'],
  553. 'admin_user': ks_ncc_rel['service_username'],
  554. 'admin_password': ks_ncc_rel['service_password'],
  555. }
  556. expected['DEFAULT'].update({
  557. 'lock_path': '/var/lock/nova',
  558. 'libvirt_use_virtio_for_bridges': 'True',
  559. 'compute_driver': 'libvirt.LibvirtDriver',
  560. 'rabbit_userid': 'nova',
  561. 'rabbit_virtual_host': 'openstack',
  562. 'rabbit_password': rmq_ncc_rel['password'],
  563. 'rabbit_host': rmq_ncc_rel['hostname'],
  564. 'glance_api_servers': gl_ncc_rel['glance-api-server']
  565. })
  566. else:
  567. # Kilo and later
  568. expected['database'] = {
  569. 'connection': db_uri,
  570. 'max_pool_size': u.not_null,
  571. }
  572. expected['glance'] = {
  573. 'api_servers': gl_ncc_rel['glance-api-server'],
  574. }
  575. expected['keystone_authtoken'] = {
  576. 'identity_uri': id_uri.rstrip('/'),
  577. 'auth_uri': ks_uri,
  578. 'admin_tenant_name': ks_ncc_rel['service_tenant'],
  579. 'admin_user': ks_ncc_rel['service_username'],
  580. 'admin_password': ks_ncc_rel['service_password'],
  581. 'signing_dir': '/var/cache/nova',
  582. }
  583. expected['osapi_v3'] = {
  584. 'enabled': 'True',
  585. }
  586. # due to worker multiplier changes and the way the unit changes
  587. # depending on whether it is LXC or KVM, we can't actually guess
  588. # the workers reliable.
  589. expected['conductor'] = {
  590. 'workers': u.not_null,
  591. }
  592. expected['oslo_messaging_rabbit'] = {
  593. 'rabbit_userid': 'nova',
  594. 'rabbit_virtual_host': 'openstack',
  595. 'rabbit_password': rmq_ncc_rel['password'],
  596. 'rabbit_host': rmq_ncc_rel['hostname'],
  597. }
  598. expected['oslo_concurrency'] = {
  599. 'lock_path': '/var/lock/nova',
  600. }
  601. if self._get_openstack_release() >= self.trusty_mitaka:
  602. expected['keystone_authtoken'] = {
  603. 'auth_uri': ks_uri.rstrip('/'),
  604. 'auth_url': id_uri.rstrip('/'),
  605. 'auth_type': 'password',
  606. 'project_domain_name': 'default',
  607. 'user_domain_name': 'default',
  608. 'project_name': 'services',
  609. 'username': ks_ncc_rel['service_username'],
  610. 'password': ks_ncc_rel['service_password'],
  611. 'signing_dir': '/var/cache/nova'
  612. }
  613. elif self._get_openstack_release() >= self.trusty_liberty:
  614. # Liberty
  615. expected['keystone_authtoken'] = {
  616. 'auth_uri': ks_uri.rstrip('/'),
  617. 'auth_url': id_uri.rstrip('/'),
  618. 'auth_plugin': 'password',
  619. 'project_domain_id': 'default',
  620. 'user_domain_id': 'default',
  621. 'project_name': 'services',
  622. 'username': 'nova',
  623. 'password': ks_ncc_rel['service_password'],
  624. 'signing_dir': '/var/cache/nova',
  625. }
  626. if self._get_openstack_release() < self.trusty_mitaka:
  627. expected['DEFAULT'].update({
  628. 'ec2_private_dns_show_ip': 'True',
  629. 'enabled_apis': 'ec2,osapi_compute,metadata',
  630. 'keystone_ec2_url': ks_ec2,
  631. 'ec2_listen_port': '8763'
  632. })
  633. elif self._get_openstack_release() >= self.trusty_mitaka:
  634. expected['DEFAULT'].update({
  635. 'enabled_apis': 'osapi_compute,metadata',
  636. })
  637. if self._get_openstack_release() >= self.xenial_ocata:
  638. del expected['DEFAULT']['force_dhcp_release']
  639. del expected['DEFAULT']['network_manager']
  640. del expected['oslo_messaging_rabbit']
  641. expected['DEFAULT']['transport_url'] = u.not_null
  642. del expected['DEFAULT']['auth_strategy']
  643. expected['api'] = {'auth_strategy': 'keystone'}
  644. del expected['DEFAULT']['api_paste_config']
  645. expected['wsgi'] = {'api_paste_config': '/etc/nova/api-paste.ini'}
  646. for section, pairs in expected.iteritems():
  647. ret = u.validate_config_data(unit, conf, section, pairs)
  648. if ret:
  649. message = "nova config error: {}".format(ret)
  650. amulet.raise_status(amulet.FAIL, msg=message)
  651. def test_302_api_rate_limiting_is_enabled(self):
  652. """
  653. Check that API rate limiting is enabled.
  654. """
  655. u.log.debug('Checking api-paste config file data...')
  656. unit = self.nova_cc_sentry
  657. conf = '/etc/nova/api-paste.ini'
  658. if self._get_openstack_release() >= self.trusty_mitaka:
  659. section = "filter:legacy_ratelimit"
  660. else:
  661. section = "filter:ratelimit"
  662. factory = ("nova.api.openstack.compute.limits:RateLimitingMiddleware"
  663. ".factory")
  664. expected = {"paste.filter_factory": factory,
  665. "limits": "( POST, '*', .*, 9999, MINUTE );"}
  666. ret = u.validate_config_data(unit, conf, section, expected)
  667. if ret:
  668. message = "api paste config error: {}".format(ret)
  669. amulet.raise_status(amulet.FAIL, msg=message)
  670. def test_400_image_instance_create(self):
  671. """Create an image/instance, verify they exist, and delete them."""
  672. u.log.debug('Checking nova instance creation...')
  673. image = u.create_cirros_image(self.glance, "cirros-image")
  674. if not image:
  675. amulet.raise_status(amulet.FAIL, msg="Image create failed")
  676. # Ensure required flavor exists, required for >= newton
  677. u.create_flavor(nova=self.nova,
  678. name='m1.tiny', ram=512, vcpus=1, disk=1)
  679. instance = u.create_instance(self.nova_demo, "cirros-image", "cirros",
  680. "m1.tiny")
  681. if not instance:
  682. amulet.raise_status(amulet.FAIL, msg="Instance create failed")
  683. found = False
  684. for instance in self.nova_demo.servers.list():
  685. if instance.name == 'cirros':
  686. found = True
  687. if instance.status != 'ACTIVE':
  688. msg = "cirros instance is not active"
  689. amulet.raise_status(amulet.FAIL, msg=msg)
  690. if not found:
  691. message = "nova cirros instance does not exist"
  692. amulet.raise_status(amulet.FAIL, msg=message)
  693. u.delete_resource(self.glance.images, image.id,
  694. msg="glance image")
  695. u.delete_resource(self.nova_demo.servers, instance.id,
  696. msg="nova instance")
  697. def test_900_restart_on_config_change(self):
  698. """Verify that the specified services are restarted when the config
  699. is changed."""
  700. u.log.info('Checking that conf files and system services respond '
  701. 'to a charm config change...')
  702. sentry = self.nova_cc_sentry
  703. juju_service = 'nova-cloud-controller'
  704. # Process names, corresponding conf files
  705. conf_file = '/etc/nova/nova.conf'
  706. services = {
  707. 'nova-api-ec2': conf_file,
  708. 'nova-api-os-compute': conf_file,
  709. 'nova-objectstore': conf_file,
  710. 'nova-cert': conf_file,
  711. 'nova-scheduler': conf_file,
  712. 'nova-conductor': conf_file
  713. }
  714. cmp_os_release = CompareOpenStackReleases(
  715. self._get_openstack_release_string()
  716. )
  717. if cmp_os_release >= 'liberty':
  718. del services['nova-api-ec2']
  719. del services['nova-objectstore']
  720. if cmp_os_release >= 'newton':
  721. del services['nova-cert']
  722. if self._get_openstack_release() >= self.xenial_ocata:
  723. # nova-placement-api is run under apache2 with mod_wsgi
  724. services['apache2'] = conf_file
  725. # Expected default and alternate values
  726. flags_default = 'quota_cores=20,quota_instances=40,quota_ram=102400'
  727. flags_alt = 'quota_cores=10,quota_instances=20,quota_ram=51200'
  728. set_default = {'config-flags': flags_default}
  729. set_alternate = {'config-flags': flags_alt}
  730. # Make config change, check for service restarts
  731. u.log.debug('Making config change on {}...'.format(juju_service))
  732. mtime = u.get_sentry_time(sentry)
  733. self.d.configure(juju_service, set_alternate)
  734. sleep_time = 60
  735. for s, conf_file in services.iteritems():
  736. u.log.debug("Checking that service restarted: {}".format(s))
  737. if not u.validate_service_config_changed(sentry, mtime, s,
  738. conf_file,
  739. sleep_time=sleep_time):
  740. self.d.configure(juju_service, set_default)
  741. msg = "service {} didn't restart after config change".format(s)
  742. amulet.raise_status(amulet.FAIL, msg=msg)
  743. sleep_time = 0
  744. self.d.configure(juju_service, set_default)
  745. def test_901_pause_resume(self):
  746. """Test pause and resume actions."""
  747. self._assert_services(should_run=True)
  748. action_id = u.run_action(self.nova_cc_sentry, "pause")
  749. assert u.wait_on_action(action_id), "Pause action failed."
  750. self._assert_services(should_run=False)
  751. action_id = u.run_action(self.nova_cc_sentry, "resume")
  752. assert u.wait_on_action(action_id), "Resume action failed"
  753. self._assert_services(should_run=True)