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 37KB

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