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:
parent
f5922e1992
commit
eadfd6538a
|
@ -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):
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -23,6 +23,7 @@ they are used.
|
|||
CREATE = 'create'
|
||||
DELETE = 'delete'
|
||||
REBOOT = 'reboot'
|
||||
REBUILD = 'rebuild'
|
||||
STOP = 'stop'
|
||||
START = 'start'
|
||||
PAUSE = 'pause'
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue