Add support for cells v2

This change adds relations necessary for registering a compute cell
with the superconductor. For a cell to be registered this charm
must have relations with the compute cells conductor, database and
message queue. Only when all these relations are complete can the
registration happen. Below are major changes included in this PR.

* Add nova-cell-api relation for communicating with the
  nova-cell-conductor
* Add shared-db-cell relation for communicating with the
  a compute cells database.
* Add amqp-cell relation for communicating with the
  a compute cells message queue.
* Add methods for registering cells with the
  superconductors database.
* Charm helper sync

Change-Id: Ic6ddc29426319b98b147c29031f60485fccc513f
This commit is contained in:
Liam Young 2018-06-26 14:48:23 +00:00
parent d5c5cccb0a
commit 6695d79c95
16 changed files with 416 additions and 41 deletions

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -14,6 +14,7 @@
import os
import re
import six
import subprocess
@ -95,6 +96,8 @@ class ApacheConfContext(object):
ctxt = settings['hardening']
out = subprocess.check_output(['apache2', '-v'])
if six.PY3:
out = out.decode('utf-8')
ctxt['apache_version'] = re.search(r'.+version: Apache/(.+?)\s.+',
out).group(1)
ctxt['apache_icondir'] = '/usr/share/apache2/icons/'

View File

@ -15,7 +15,7 @@
import re
import subprocess
from six import string_types
import six
from charmhelpers.core.hookenv import (
log,
@ -35,7 +35,7 @@ class DisabledModuleAudit(BaseAudit):
def __init__(self, modules):
if modules is None:
self.modules = []
elif isinstance(modules, string_types):
elif isinstance(modules, six.string_types):
self.modules = [modules]
else:
self.modules = modules
@ -69,6 +69,8 @@ class DisabledModuleAudit(BaseAudit):
def _get_loaded_modules():
"""Returns the modules which are enabled in Apache."""
output = subprocess.check_output(['apache2ctl', '-M'])
if six.PY3:
output = output.decode('utf-8')
modules = []
for line in output.splitlines():
# Each line of the enabled module output looks like:

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -105,6 +105,7 @@ from nova_cc_utils import (
ssh_compute_remove,
ssh_known_hosts_lines,
ssh_authorized_keys_lines,
update_child_cell,
register_configs,
restart_map,
update_cell_database,
@ -192,12 +193,8 @@ def leader_init_db_if_ready(skip_acl_check=False, db_rid=None, unit=None):
allowed_units.split()):
status_set('maintenance', 'Running nova db migration')
migrate_nova_databases()
log('Triggering remote cloud-compute restarts.')
[compute_joined(rid=rid, remote_restart=True)
for rid in relation_ids('cloud-compute')]
log('Triggering remote neutron-network-service restarts.')
[quantum_joined(rid=rid, remote_restart=True)
for rid in relation_ids('quantum-network-service')]
log('Triggering remote restarts.')
update_nova_relation(remote_restart=True)
else:
log('allowed_units either not presented, or local unit '
'not in acl list: %s' % repr(allowed_units))
@ -252,6 +249,12 @@ def update_cell_db_if_ready_allowed_units():
update_cell_db_if_ready(db_rid=rid, unit=unit)
def update_child_cell_records():
for r_id in relation_ids('nova-cell-api'):
for unit in related_units(relid=r_id):
nova_cell_api_relation_changed(rid=r_id, unit=unit)
@hooks.hook('install.real')
@harden()
def install():
@ -334,12 +337,10 @@ def config_changed():
if filtered:
apt_install(filtered, fatal=True)
for rid in relation_ids('quantum-network-service'):
quantum_joined(rid=rid)
for r_id in relation_ids('identity-service'):
identity_joined(rid=r_id)
[cluster_joined(rid) for rid in relation_ids('cluster')]
[compute_joined(rid=rid) for rid in relation_ids('cloud-compute')]
update_nova_relation()
update_nrpe_config()
@ -384,6 +385,8 @@ def amqp_changed():
for r_id in relation_ids('nova-api'):
nova_api_relation_joined(rid=r_id)
update_child_cell_records()
# NOTE: trigger restart on nova-api-metadata on
# neutron-gateway units once nova-cc has working
# amqp connection (avoiding service down on n-gateway)
@ -455,6 +458,7 @@ def db_changed():
# be set in nova.conf, so we attempt db init in here as well as the
# amqp-relation-changed hook.
update_cell_db_if_ready()
update_child_cell_records()
@hooks.hook('image-service-relation-changed')
@ -498,8 +502,7 @@ def identity_changed():
return
CONFIGS.write('/etc/nova/api-paste.ini')
CONFIGS.write(NOVA_CONF)
[compute_joined(rid) for rid in relation_ids('cloud-compute')]
[quantum_joined(rid) for rid in relation_ids('quantum-network-service')]
update_nova_relation()
[nova_vmware_relation_joined(rid) for rid in relation_ids('nova-vmware')]
[neutron_api_relation_joined(rid) for rid in relation_ids('neutron-api')]
configure_https()
@ -637,8 +640,7 @@ def console_settings():
return rel_settings
@hooks.hook('cloud-compute-relation-joined')
def compute_joined(rid=None, remote_restart=False):
def get_compute_config(rid=None, remote_restart=False):
cons_settings = console_settings()
relation_set(relation_id=rid, **cons_settings)
rel_settings = {
@ -655,6 +657,21 @@ def compute_joined(rid=None, remote_restart=False):
if remote_restart:
rel_settings['restart_trigger'] = str(uuid.uuid4())
return rel_settings
def update_nova_relation(remote_restart=False):
for rid in relation_ids('cloud-compute'):
compute_joined(rid=rid, remote_restart=remote_restart)
for rid in relation_ids('quantum-network-service'):
quantum_joined(rid=rid, remote_restart=remote_restart)
for rid in relation_ids('nova-cell-api'):
nova_cell_api_relation_joined(rid=rid, remote_restart=remote_restart)
@hooks.hook('cloud-compute-relation-joined')
def compute_joined(rid=None, remote_restart=False):
rel_settings = get_compute_config(rid=rid, remote_restart=remote_restart)
rel_settings.update(keystone_compute_settings())
relation_set(relation_id=rid, **rel_settings)
@ -1010,10 +1027,7 @@ def neutron_api_relation_joined(rid=None, remote_restart=False):
@restart_on_change(restart_map())
def neutron_api_relation_changed():
CONFIGS.write(NOVA_CONF)
for rid in relation_ids('cloud-compute'):
compute_joined(rid=rid)
for rid in relation_ids('quantum-network-service'):
quantum_joined(rid=rid)
update_nova_relation()
@hooks.hook('neutron-api-relation-broken')
@ -1022,10 +1036,7 @@ def neutron_api_relation_changed():
@restart_on_change(restart_map())
def neutron_api_relation_broken():
CONFIGS.write_all()
for rid in relation_ids('cloud-compute'):
compute_joined(rid=rid)
for rid in relation_ids('quantum-network-service'):
quantum_joined(rid=rid)
update_nova_relation()
@hooks.hook('nrpe-external-master-relation-joined',
@ -1151,6 +1162,65 @@ def certs_changed(relation_id=None, unit=None):
configure_https()
@hooks.hook('amqp-cell-relation-joined')
def amqp_cell_joined(relation_id=None):
relation_set(relation_id=relation_id,
username='nova', vhost='nova')
@hooks.hook('shared-db-cell-relation-joined')
def shared_db_cell_joined(relation_id=None):
access_network = None
for unit in related_units(relid=relation_id):
access_network = relation_get(rid=relation_id, unit=unit,
attribute='access-network')
if access_network:
break
host = get_relation_ip('shared-db', cidr_network=access_network)
cell_db = {
'nova_database': 'nova',
'nova_username': config('database-user'),
'nova_hostname': host}
relation_set(relation_id=relation_id, **cell_db)
@hooks.hook('nova-cell-api-relation-joined')
def nova_cell_api_relation_joined(rid=None, remote_restart=False):
rel_settings = get_compute_config(rid=rid, remote_restart=remote_restart)
if network_manager() == 'neutron':
rel_settings.update(neutron_settings())
relation_set(relation_id=rid, **rel_settings)
@hooks.hook('shared-db-cell-relation-changed')
def shared_db_cell_changed(relation_id=None):
update_child_cell_records()
@hooks.hook('amqp-cell-relation-changed')
def amqp_cell_changed(relation_id=None):
update_child_cell_records()
@hooks.hook('nova-cell-api-relation-changed')
def nova_cell_api_relation_changed(rid=None, unit=None):
data = relation_get(rid=rid, unit=unit)
log("Data: {}".format(data, level=DEBUG))
if not data.get('cell-name'):
return
cell_updated = update_child_cell(
name=data['cell-name'],
db_service=data['db-service'],
amqp_service=data['amqp-service'])
if cell_updated:
log(
"Cell registration data changed, triggering a remote restart",
level=DEBUG)
relation_set(
relation_id=rid,
restart_trigger=str(uuid.uuid4()))
@hooks.hook('update-status')
@harden()
def update_status():

View File

@ -15,6 +15,7 @@
import os
import subprocess
import ConfigParser
import uuid
from base64 import b64encode
from collections import OrderedDict
@ -72,6 +73,7 @@ from charmhelpers.core.hookenv import (
leader_get,
leader_set,
relation_get,
relation_id,
relation_ids,
remote_unit,
DEBUG,
@ -88,6 +90,7 @@ from charmhelpers.core.host import (
service_running,
service_start,
service_stop,
service_restart,
lsb_release,
CompareHostReleases,
)
@ -791,23 +794,48 @@ def initialize_cell_databases():
'the transport_url/database combination.', level=INFO)
def get_cell_uuid(cell):
def get_cell_uuid(cell, fatal=True):
'''Get cell uuid
:param cell: string cell name i.e. 'cell1'
:returns: string cell uuid
'''
log("Listing cell, '{}'".format(cell), level=INFO)
cmd = ['sudo', 'nova-manage', 'cell_v2', 'list_cells']
cells = get_cell_details()
cell_info = cells.get(cell)
if not cell_info:
if fatal:
raise Exception("Cannot find cell, '{}', in list_cells."
"".format(cell))
return None
return cell_info['uuid']
def get_cell_details():
'''Get cell details
:returns: string cell uuid
'''
log("Getting details of cells", level=INFO)
cells = {}
cmd = ['sudo', 'nova-manage', 'cell_v2', 'list_cells', '--verbose']
try:
out = subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
log('list_cells failed\n{}'.format(e.output), level=ERROR)
raise
cell_uuid = out.split(cell, 1)[1].split()[1]
if not cell_uuid:
raise Exception("Cannot find cell, '{}', in list_cells."
"".format(cell))
return cell_uuid
for line in out.split('\n'):
columns = line.split('|')
if len(columns) < 2:
continue
columns = [c.strip() for c in columns]
try:
uuid.UUID(columns[2].strip())
cells[columns[1]] = {
'uuid': columns[2],
'amqp': columns[3],
'db': columns[4]}
except ValueError:
pass
return cells
def update_cell_database():
@ -1480,4 +1508,110 @@ def write_vendordata(vdata):
return False
with open(VENDORDATA_FILE, 'w') as vdata_file:
vdata_file.write(json.dumps(json_vdata, sort_keys=True, indent=2))
def get_cell_db_context(db_service):
"""Return the database context for the given service name"""
db_rid = relation_id(
relation_name='shared-db-cell',
service_or_unit=db_service)
return context.SharedDBContext(
relation_prefix='nova',
ssl_dir=NOVA_CONF_DIR,
relation_id=db_rid)()
def get_cell_amqp_context(amqp_service):
"""Return the amqp context for the given service name"""
amq_rid = relation_id(
relation_name='amqp-cell',
service_or_unit=amqp_service)
return context.AMQPContext(
ssl_dir=NOVA_CONF_DIR,
relation_id=amq_rid)()
def get_sql_uri(db_ctxt):
"""Return the uri for conextind to the database in the supplied context"""
uri_template = ("{database_type}://{database_user}:{database_password}"
"@{database_host}/{database}")
uri = uri_template.format(**db_ctxt)
if db_ctxt.get('database_ssl_ca'):
uri = uri + '?ssl_ca={database_ssl_ca}'.format(**db_ctxt)
if db_ctxt.get('database_ssl_cert'):
uri = uri + ('&ssl_cert={database_ssl_cert}'
'&ssl_key={database_ssl_key}').format(**db_ctxt)
return uri
def update_child_cell(name, db_service, amqp_service, skip_acl_check=True):
"""Register cell.
Registering a cell requires:
1) Complete relation with api db service.
2) Complete relation with cells db service.
3) Complete relation with cells amqp service.
"""
if not is_db_initialised():
log(
'Defering registering Cell {}, api db not ready.'.format(name),
level=DEBUG)
return False
existing_cells = get_cell_details()
if not existing_cells.get('cell1'):
log('Defering registering cell {}, api cell setup is not complete.'
''.format(name),
level=DEBUG)
return False
db_ctxt = get_cell_db_context(db_service)
if not db_ctxt:
log('Defering registering cell {}, cell db relation not '
'ready.'.format(name),
level=DEBUG)
return False
sql_connection = get_sql_uri(db_ctxt)
amqp_ctxt = get_cell_amqp_context(amqp_service)
if not amqp_ctxt:
log('Defering registering cell {}, cell amqp relation not '
'ready.'.format(name),
level=DEBUG)
return False
cmd = [
'nova-manage',
'cell_v2',
]
if existing_cells.get(name):
log('Cell {} already registered, checking if details are correct.'
''.format(name), level=DEBUG)
if (amqp_ctxt['transport_url'] == existing_cells[name]['amqp'] and
sql_connection == existing_cells[name]['db']):
log('Cell details are correct no update needed', level=DEBUG)
return False
else:
log('Cell details have changed', level=DEBUG)
cmd.extend([
'update_cell',
'--cell_uuid', existing_cells[name]['uuid']])
else:
log(
'Cell {} is new and needs to be created.'.format(name),
level=DEBUG)
cmd.extend(['create_cell', '--verbose'])
cmd.extend([
'--name', name,
'--transport-url', amqp_ctxt['transport_url'],
'--database_connection', sql_connection])
try:
log('Updating cell {}'.format(name), level=DEBUG)
subprocess.check_output(cmd)
except subprocess.CalledProcessError as e:
log('Register cell failed\n{}'.format(e.output), level=ERROR)
raise
service_restart('nova-scheduler')
return True

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -0,0 +1 @@
nova_cc_hooks.py

View File

@ -30,6 +30,8 @@ provides:
scope: container
cloud-controller:
interface: nova
nova-cell-api:
interface: nova-cell
requires:
shared-db:
interface: mysql-shared
@ -57,6 +59,10 @@ requires:
interface: memcache
certificates:
interface: tls-certificates
shared-db-cell:
interface: mysql-shared
amqp-cell:
interface: rabbitmq
peers:
cluster:
interface: nova-ha

View File

@ -693,7 +693,7 @@ class NovaCCHooksTests(CharmTestCase):
def _relation_ids(rel):
relid = {
'cloud-compute': ['nova-compute/0'],
'cell': ['nova-cell-api/0'],
'nova-cell-api': ['nova-cell-controller/0'],
'neutron-api': ['neutron-api/0'],
'quantum-network-service': ['neutron-gateway/0']
}
@ -721,6 +721,7 @@ class NovaCCHooksTests(CharmTestCase):
hooks.relation_broken()
self.assertTrue(configs.write_all.called)
@patch.object(hooks, 'update_child_cell_records')
@patch.object(hooks, 'leader_init_db_if_ready_allowed_units')
@patch.object(hooks, 'update_cell_db_if_ready_allowed_units')
@patch.object(hooks, 'is_db_initialised')
@ -729,7 +730,8 @@ class NovaCCHooksTests(CharmTestCase):
@patch.object(hooks, 'CONFIGS')
def test_amqp_changed_api_rel(self, configs, api_joined,
quantum_joined, mock_is_db_initialised,
update_db_allowed, init_db_allowed):
update_db_allowed, init_db_allowed,
mock_update_child_cell_records):
self.relation_ids.side_effect = [
['nova-api/0'],
['quantum-service/0'],
@ -747,6 +749,7 @@ class NovaCCHooksTests(CharmTestCase):
quantum_joined.assert_called_with(rid='quantum-service/0',
remote_restart=True)
@patch.object(hooks, 'update_child_cell_records')
@patch.object(hooks, 'leader_init_db_if_ready_allowed_units')
@patch.object(hooks, 'update_cell_db_if_ready_allowed_units')
@patch.object(hooks, 'is_db_initialised')
@ -755,7 +758,8 @@ class NovaCCHooksTests(CharmTestCase):
@patch.object(hooks, 'CONFIGS')
def test_amqp_changed_noapi_rel(self, configs, api_joined,
quantum_joined, mock_is_db_initialised,
update_db_allowed, init_db_allowed):
update_db_allowed, init_db_allowed,
mock_update_child_cell_records):
mock_is_db_initialised.return_value = False
configs.complete_contexts = MagicMock()
configs.complete_contexts.return_value = ['amqp']

View File

@ -198,6 +198,15 @@ ubuntu-cloud-archive/liberty-staging/ubuntu trusty main
%s
""" % GPG_PPA_CLOUD_ARCHIVE
NM_CELLS_LIST = """
+-------+--------------------------------------+--------------+-------------+
| Name | UUID | Transport | DB |
+-------+--------------------------------------+--------------+-------------+
| cell0 | 00000000-0000-0000-0000-000000000000 | none:/// | mysql_cell0 |
| cell1 | 7a8a0e58-e127-4056-bb98-99d9579ca08b | rabbit_cell1 | mysql_cell1 |
+-------+--------------------------------------+--------------+-------------+
"""
class NovaCCUtilsTests(CharmTestCase):
@ -1123,14 +1132,8 @@ class NovaCCUtilsTests(CharmTestCase):
@patch('subprocess.check_output')
def test_get_cell_uuid(self, mock_check_call):
mock_check_call.return_value = ("""
+-------+--------------------------------------+
| Name | UUID |
+-------+--------------------------------------+
| cell0 | 00000000-0000-0000-0000-000000000000 |
| cell1 | c83121db-f1c7-464a-b657-38c28fac84c6 |
+-------+--------------------------------------+""")
expected = 'c83121db-f1c7-464a-b657-38c28fac84c6'
mock_check_call.return_value = NM_CELLS_LIST
expected = '7a8a0e58-e127-4056-bb98-99d9579ca08b'
self.assertEqual(expected, utils.get_cell_uuid('cell1'))
@patch.object(utils, 'get_cell_uuid')
@ -1315,3 +1318,147 @@ class NovaCCUtilsTests(CharmTestCase):
call().write('{\n "a": "b"\n}')]
for c in expected_calls:
self.assertTrue(c in m.mock_calls)
@patch.object(utils.context, 'SharedDBContext')
@patch.object(utils, 'relation_id')
def test_get_cell_db_context(self, mock_relation_id, mock_SharedDBContext):
mock_relation_id.return_value = 'dbid'
utils.get_cell_db_context('mysql-cell2')
mock_SharedDBContext.assert_called_once_with(
relation_id='dbid',
relation_prefix='nova',
ssl_dir='/etc/nova')
mock_relation_id.assert_called_once_with(
relation_name='shared-db-cell',
service_or_unit='mysql-cell2')
@patch.object(utils.context, 'AMQPContext')
@patch.object(utils, 'relation_id')
def test_get_cell_amqp_context(self, mock_relation_id, mock_AMQPContext):
mock_relation_id.return_value = 'amqpid'
utils.get_cell_amqp_context('rabbitmq-server-cell2')
mock_AMQPContext.assert_called_once_with(
relation_id='amqpid',
ssl_dir='/etc/nova')
mock_relation_id.assert_called_once_with(
relation_name='amqp-cell',
service_or_unit='rabbitmq-server-cell2')
def test_get_sql_uri(self):
base_ctxt = {
'database_type': 'mysql',
'database_user': 'nova',
'database_password': 'novapass',
'database_host': '10.0.0.10',
'database': 'novadb'}
self.assertEqual(
utils.get_sql_uri(base_ctxt),
'mysql://nova:novapass@10.0.0.10/novadb')
sslca_ctxt = {'database_ssl_ca': 'myca'}
sslca_ctxt.update(base_ctxt)
self.assertEqual(
utils.get_sql_uri(sslca_ctxt),
'mysql://nova:novapass@10.0.0.10/novadb?ssl_ca=myca')
ssl_cert_ctxt = {
'database_ssl_cert': 'mycert',
'database_ssl_key': 'mykey'}
ssl_cert_ctxt.update(sslca_ctxt)
self.assertEqual(
utils.get_sql_uri(ssl_cert_ctxt),
('mysql://nova:novapass@10.0.0.10/novadb?ssl_ca=myca&'
'ssl_cert=mycert&ssl_key=mykey'))
@patch.object(utils, 'is_db_initialised')
@patch.object(utils, 'get_cell_details')
@patch.object(utils, 'get_cell_db_context')
@patch.object(utils, 'get_cell_amqp_context')
@patch.object(utils, 'get_sql_uri')
@patch.object(utils.subprocess, 'check_output')
@patch.object(utils, 'service_restart')
def test_update_child_cell(self, mock_service_restart, mock_check_output,
mock_get_sql_uri, mock_get_cell_amqp_context,
mock_get_cell_db_context, mock_get_cell_details,
mock_is_db_initialised):
mock_is_db_initialised.return_value = True
mock_get_cell_details.return_value = {'cell1': 'cell1uuid'}
mock_get_cell_db_context.return_value = {'ctxt': 'a full context'}
mock_get_cell_amqp_context.return_value = {'transport_url': 'amqp-uri'}
mock_get_sql_uri.return_value = 'db-uri'
utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
mock_get_cell_amqp_context.assert_called_once_with('amqp-cell2')
mock_get_cell_db_context.assert_called_once_with('mysql-cell2')
mock_check_output.assert_called_once_with([
'nova-manage',
'cell_v2',
'create_cell',
'--verbose',
'--name', 'cell2',
'--transport-url', 'amqp-uri',
'--database_connection', 'db-uri'])
mock_service_restart.assert_called_once_with('nova-scheduler')
@patch.object(utils, 'is_db_initialised')
@patch.object(utils.subprocess, 'check_output')
@patch.object(utils, 'service_restart')
def test_update_child_cell_no_local_db(self, mock_service_restart,
mock_check_output,
mock_is_db_initialised):
mock_is_db_initialised.return_value = False
utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
self.assertFalse(mock_check_output.called)
self.assertFalse(mock_service_restart.called)
@patch.object(utils, 'get_cell_details')
@patch.object(utils, 'is_db_initialised')
@patch.object(utils.subprocess, 'check_output')
@patch.object(utils, 'service_restart')
def test_update_child_cell_api_cell_not_registered(self,
mock_service_restart,
mock_check_output,
mock_is_db_initialised,
mock_get_cell_details):
mock_is_db_initialised.return_value = True
mock_get_cell_details.return_value = {}
utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
mock_get_cell_details.assert_called_once_with()
self.assertFalse(mock_check_output.called)
self.assertFalse(mock_service_restart.called)
@patch.object(utils.subprocess, 'check_output')
@patch.object(utils, 'service_restart')
@patch.object(utils, 'get_cell_details')
@patch.object(utils, 'is_db_initialised')
@patch.object(utils, 'get_cell_db_context')
def test_update_child_cell_no_cell_db(self, mock_get_cell_db_context,
mock_is_db_initialised,
mock_get_cell_details,
mock_service_restart,
mock_check_output):
mock_is_db_initialised.return_value = True
mock_get_cell_details.return_value = {'cell1': 'uuid4cell1'}
mock_get_cell_db_context.return_value = {}
utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
self.assertFalse(mock_check_output.called)
self.assertFalse(mock_service_restart.called)
@patch.object(utils, 'get_cell_amqp_context')
@patch.object(utils, 'get_sql_uri')
@patch.object(utils.subprocess, 'check_output')
@patch.object(utils, 'service_restart')
@patch.object(utils, 'get_cell_details')
@patch.object(utils, 'is_db_initialised')
@patch.object(utils, 'get_cell_db_context')
def test_update_child_cell_no_cell_amqp(self, mock_get_cell_db_context,
mock_is_db_initialised,
mock_get_cell_details,
mock_service_restart,
mock_check_output,
mock_get_sql_uri,
mock_get_cell_amqp_context):
mock_is_db_initialised.return_value = True
mock_get_cell_details.return_value = {'cell1': 'uuid4cell1'}
mock_get_cell_db_context.return_value = {'ctxt': 'a full context'}
mock_get_cell_amqp_context.return_value = {}
utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
self.assertFalse(mock_check_output.called)
self.assertFalse(mock_service_restart.called)