zun rebuild on local node

Provide reconstruction function, if the container A is damaged,
users can use this function to recreate a container B.
The container B and A use the same resources, such as network, CPU, etc.

Change-Id: Ie51bf68f5f94d847596ce17cecf5550bbdd97ac8
Implements:blueprint zun-rebuild-on-local-node
This commit is contained in:
JiWei 2018-03-10 22:27:54 +08:00
parent f5922e1992
commit eadfd6538a
12 changed files with 165 additions and 3 deletions

View File

@ -157,6 +157,7 @@ class ContainersController(base.Controller):
'start': ['POST'],
'stop': ['POST'],
'reboot': ['POST'],
'rebuild': ['POST'],
'pause': ['POST'],
'unpause': ['POST'],
'logs': ['GET'],
@ -668,6 +669,63 @@ class ContainersController(base.Controller):
compute_api.container_delete(context, container, force)
pecan.response.status = 204
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def rebuild(self, container_ident, **kwargs):
"""Rebuild container.
:param container_ident: UUID or Name of a container.
"""
container = utils.get_container(container_ident)
check_policy_on_container(container.as_dict(), "container:rebuild")
utils.validate_container_state(container, 'rebuild')
LOG.debug('Calling compute.container_rebuild with %s',
container.uuid)
context = pecan.request.context
network_info = self._get_network_info(context, container)
vol_info = self._get_vol_info(context, container)
compute_api = pecan.request.compute_api
compute_api.container_rebuild(context, container,
network_info, vol_info)
pecan.response.status = 202
def _get_vol_info(self, context, container):
volumes = objects.VolumeMapping.list_by_container(context,
container.uuid)
return volumes
def _get_network_info(self, context, container):
neutron_api = neutron.NeutronAPI(context)
network_info = []
for i in range(len(container.addresses)):
try:
network_id = container.addresses.keys()[i]
addr_info = container.addresses.values()[i][0]
port_id = addr_info.get('port')
neutron_api.get_neutron_port(port_id)
network = neutron_api.get_neutron_network(network_id)
except exception.PortNotFound:
LOG.exception("The port: %s used by the source container "
"does not exist, can not rebuild", port_id)
raise
except exception.NetworkNotFound:
LOG.exception("The network: %s used by the source container "
"does not exist, can not rebuild", network_id)
raise
except Exception as e:
LOG.exception("Unexpected exception: %s", e)
raise
preserve_info = addr_info.get('preserve_on_delete')
network_info.append({'network': network_id,
'port': port_id,
'router:external':
network.get('router:external'),
'shared': network.get('shared'),
'v4-fixed-ip': '',
'v6-fixed-ip': '',
'preserve_on_delete': preserve_info})
return network_info
@pecan.expose('json')
@exception.wrap_pecan_controller_exception
def start(self, container_ident, **kwargs):

View File

@ -13,10 +13,10 @@
CONTAINER_STATUSES = (
ERROR, RUNNING, STOPPED, PAUSED, UNKNOWN, CREATING, CREATED,
DELETED, DELETING
DELETED, DELETING, REBUILDING
) = (
'Error', 'Running', 'Stopped', 'Paused', 'Unknown', 'Creating', 'Created',
'Deleted', 'Deleting'
'Deleted', 'Deleting', 'Rebuilding'
)
CAPSULE_STATUSES = (

View File

@ -373,6 +373,17 @@ rules = [
}
]
),
policy.DocumentedRuleDefault(
name=CONTAINER % 'rebuild',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Rebuild a container.',
operations=[
{
'path': '/v1/containers/{container_ident}/rebuild',
'method': 'POST'
}
]
),
]

View File

@ -58,6 +58,7 @@ VALID_STATES = {
'start': [consts.CREATED, consts.STOPPED, consts.ERROR],
'stop': [consts.RUNNING],
'reboot': [consts.CREATED, consts.RUNNING, consts.STOPPED, consts.ERROR],
'rebuild': [consts.CREATED, consts.RUNNING, consts.STOPPED, consts.ERROR],
'pause': [consts.RUNNING],
'unpause': [consts.PAUSED],
'kill': [consts.RUNNING],

View File

@ -86,6 +86,12 @@ class API(object):
def container_show(self, context, container):
return self.rpcapi.container_show(context, container)
def container_rebuild(self, context, container, network_info, vol_info):
self._record_action_start(context, container,
container_actions.REBUILD)
return self.rpcapi.container_rebuild(context, container,
network_info, vol_info)
def container_reboot(self, context, container, *args):
self._record_action_start(context, container, container_actions.REBOOT)
return self.rpcapi.container_reboot(context, container, *args)

View File

@ -23,6 +23,7 @@ they are used.
CREATE = 'create'
DELETE = 'delete'
REBOOT = 'reboot'
REBUILD = 'rebuild'
STOP = 'stop'
START = 'start'
PAUSE = 'pause'

View File

@ -531,6 +531,55 @@ class Manager(periodic_task.PeriodicTasks):
utils.spawn_n(do_container_stop)
def _update_container_state(self, context, container, container_status):
container.status = container_status
container.save(context)
def container_rebuild(self, context, container, network_info, vol_info):
@utils.synchronized(container.uuid)
def do_container_rebuild():
self._do_container_rebuild(context, container,
network_info, vol_info)
utils.spawn_n(do_container_rebuild)
def _do_container_rebuild(self, context, container,
network_info, vol_info):
LOG.info("start to rebuild container: %s", container.uuid)
ori_status = container.status
self._update_container_state(context, container, consts.REBUILDING)
if self.driver.check_container_exist(container):
for addr in container.addresses.values():
for port in addr:
port['preserve_on_delete'] = True
try:
self._update_task_state(context, container,
consts.CONTAINER_DELETING)
self.driver.delete(context, container, True)
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error("Rebuild container: %s failed, "
"reason of failure is: %s",
container.uuid,
six.text_type(e))
self._fail_container(context, container, six.text_type(e))
try:
self._update_task_state(context, container,
consts.CONTAINER_CREATING)
created_container = self._do_container_create_base(
context, container, network_info, vol_info)
self._update_container_state(context, container, consts.CREATED)
except Exception as e:
with excutils.save_and_reraise_exception():
LOG.error("Rebuild container:%s failed, "
"reason of failure is: %s", container.uuid, e)
LOG.info("rebuild container: %s success", created_container.uuid)
if ori_status == consts.RUNNING:
self._do_container_start(context, created_container)
return
def container_start(self, context, container):
@utils.synchronized(container.uuid)
def do_container_start():

View File

@ -75,6 +75,10 @@ class API(rpc_service.API):
return self._call(container.host, 'container_show',
container=container)
def container_rebuild(self, context, container, network_info, vol_info):
self._cast(container.host, 'container_rebuild', container=container,
network_info=network_info, vol_info=vol_info)
def container_reboot(self, context, container, timeout):
self._cast(container.host, 'container_reboot', container=container,
timeout=timeout)

View File

@ -302,6 +302,14 @@ class DockerDriver(driver.ContainerDriver):
network_api.disconnect_container_from_network(
container, docker_net, neutron_network_id=neutron_net)
def check_container_exist(self, container):
with docker_utils.docker_client() as docker:
docker_containers = [c['Id']
for c in docker.list_containers()]
if container.container_id not in docker_containers:
return False
return True
def list(self, context):
id_to_container_map = {}
uuids = []

View File

@ -699,6 +699,27 @@ class TestManager(base.TestCase):
self.compute_manager.container_show,
self.context, container)
@mock.patch('zun.image.driver.pull_image')
@mock.patch.object(fake_driver, 'check_container_exist')
@mock.patch.object(Container, 'save')
@mock.patch.object(fake_driver, 'create')
@mock.patch.object(fake_driver, 'delete')
def test_container_rebuild(self, mock_delete, mock_create,
mock_save, mock_check, mock_pull):
container = Container(self.context, **utils.get_test_container())
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance'}
mock_pull.return_value = image, False
networks = []
volumes = []
container.status = 'Created'
mock_check.return_value = True
self.compute_manager._do_container_rebuild(self.context, container,
networks, volumes)
mock_save.assert_called_with(self.context)
mock_delete.assert_called_once_with(self.context, container, True)
mock_create.assert_called_once_with(self.context, container, image,
networks, volumes)
@mock.patch.object(ContainerActionEvent, 'event_start')
@mock.patch.object(ContainerActionEvent, 'event_finish')
@mock.patch.object(Container, 'save')

View File

@ -112,3 +112,6 @@ class FakeDriver(driver.ContainerDriver):
def read_tar_image(self, image):
return image.get('repo'), image.get('tag')
def check_container_exist(self, context):
pass

View File

@ -344,7 +344,7 @@ class TestObject(test_base.TestCase, _TestObject):
# For more information on object version testing, read
# https://docs.openstack.org/zun/latest/
object_data = {
'Container': '1.25-365ee331fee68989272a8e462c0d9999',
'Container': '1.25-545ae961a844ea755b930c6da066eb02',
'VolumeMapping': '1.1-50df6202f7846a136a91444c38eba841',
'Image': '1.0-0b976be24f4f6ee0d526e5c981ce0633',
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',