diff --git a/api-ref/source/v1/parameters.yaml b/api-ref/source/v1/parameters.yaml index ad876e39..ef1e04ae 100644 --- a/api-ref/source/v1/parameters.yaml +++ b/api-ref/source/v1/parameters.yaml @@ -309,6 +309,12 @@ flavors: in: body required: true type: array +image_ident: + description: | + The UUID of the image to apply to rebuild your server. + in: body + required: false + type: string imageRef: description: | The UUID of the image to use for your server. diff --git a/api-ref/source/v1/samples/server_states/rebuild-server.json b/api-ref/source/v1/samples/server_states/rebuild-server.json index 309bc940..b5238957 100644 --- a/api-ref/source/v1/samples/server_states/rebuild-server.json +++ b/api-ref/source/v1/samples/server_states/rebuild-server.json @@ -1,3 +1,4 @@ { - "target": "rebuild" + "target": "rebuild", + "image_uuid":"9145be5b-38d0-4a05-8dd6-837a8ec15281" } diff --git a/api-ref/source/v1/server_states.inc b/api-ref/source/v1/server_states.inc index 835cc851..6fdd587d 100644 --- a/api-ref/source/v1/server_states.inc +++ b/api-ref/source/v1/server_states.inc @@ -129,6 +129,7 @@ Request - server_uuid: server_ident - target: provision_state + - image_uuid: image_ident **Example request to rebuild a Server:** diff --git a/mogan/api/controllers/v1/servers.py b/mogan/api/controllers/v1/servers.py index 66bffed7..73248037 100644 --- a/mogan/api/controllers/v1/servers.py +++ b/mogan/api/controllers/v1/servers.py @@ -158,9 +158,9 @@ class ServerStatesController(ServerControllerBase): pecan.request.engine_api.unlock(context, db_server) @policy.authorize_wsgi("mogan:server", "set_provision_state") - @expose.expose(None, types.uuid, wtypes.text, + @expose.expose(None, types.uuid, wtypes.text, types.uuid, status_code=http_client.ACCEPTED) - def provision(self, server_uuid, target): + def provision(self, server_uuid, target, image_uuid=None): """Asynchronous trigger the provisioning of the server. This will set the target provision state of the server, and @@ -179,10 +179,10 @@ class ServerStatesController(ServerControllerBase): raise exception.InvalidActionParameterValue( value=target, action="provision", server=server_uuid) - db_server = self._resource or self._get_resource(server_uuid) if target == states.REBUILD: - pecan.request.engine_api.rebuild(pecan.request.context, db_server) + pecan.request.engine_api.rebuild(pecan.request.context, db_server, + image_uuid) # Set the HTTP Location Header url_args = '/'.join([server_uuid, 'states']) @@ -675,7 +675,6 @@ class ServerController(ServerControllerBase): :param server: a server within the request body. """ validation.check_schema(server, server_schemas.create_server) - min_count = server.get('min_count', 1) max_count = server.get('max_count', min_count) diff --git a/mogan/baremetal/driver.py b/mogan/baremetal/driver.py index 129eb592..6135d32a 100644 --- a/mogan/baremetal/driver.py +++ b/mogan/baremetal/driver.py @@ -100,7 +100,7 @@ class BaseEngineDriver(object): """ raise NotImplementedError() - def rebuild(self, context, server): + def rebuild(self, context, server, image_uuid): """Trigger node deploy process. :param server: server to rebuild. diff --git a/mogan/baremetal/ironic/driver.py b/mogan/baremetal/ironic/driver.py index 5cb8a2d0..e87c6688 100644 --- a/mogan/baremetal/ironic/driver.py +++ b/mogan/baremetal/ironic/driver.py @@ -100,7 +100,6 @@ class IronicDriver(base_driver.BaseEngineDriver): raise exception.ServerNotFound(server=server.uuid) def _add_server_info_to_node(self, node, server): - patch = list() # Associate the node with a server patch.append({'path': '/instance_uuid', 'op': 'add', @@ -437,10 +436,14 @@ class IronicDriver(base_driver.BaseEngineDriver): """ LOG.debug('Rebuild called for server', server=server) + node_uuid = server.node_uuid + node = self._get_node(node_uuid) + self._add_server_info_to_node(node, server) + # trigger the node rebuild try: self.ironicclient.call("node.set_provision_state", - server.node_uuid, + node_uuid, ironic_states.REBUILD) except (ironic_exc.InternalServerError, ironic_exc.BadRequest) as e: diff --git a/mogan/engine/api.py b/mogan/engine/api.py index 017182b1..31b43dc0 100644 --- a/mogan/engine/api.py +++ b/mogan/engine/api.py @@ -386,8 +386,17 @@ class API(object): @check_server_lock @check_server_maintenance - def rebuild(self, context, server): + def rebuild(self, context, server, image_uuid=None): """Rebuild a server.""" + if not image_uuid: + image_uuid = server.image_uuid + # check if the image exists + self._get_image(context, image_uuid) + if image_uuid != server.image_uuid: + # replace original image with the new one + server.image_uuid = image_uuid + server.save() + fsm = utils.get_state_machine(start_state=server.status) try: utils.process_event(fsm, server, event='rebuild') diff --git a/mogan/engine/manager.py b/mogan/engine/manager.py index 5e7dc500..7e2f28bb 100644 --- a/mogan/engine/manager.py +++ b/mogan/engine/manager.py @@ -503,7 +503,6 @@ class EngineManager(base_manager.BaseEngineManager): :param context: mogan request context :param server: server object """ - LOG.debug('Rebuilding server', server=server) fsm = utils.get_state_machine(start_state=server.status) @@ -517,7 +516,6 @@ class EngineManager(base_manager.BaseEngineManager): "Exception: %(exception)s", {"uuid": server.uuid, "exception": e}) - utils.process_event(fsm, server, event='done') LOG.info('Server was successfully rebuilt', server=server) diff --git a/mogan/tests/unit/engine/test_engine_api.py b/mogan/tests/unit/engine/test_engine_api.py index 5de9d38f..80976803 100644 --- a/mogan/tests/unit/engine/test_engine_api.py +++ b/mogan/tests/unit/engine/test_engine_api.py @@ -340,21 +340,40 @@ class ComputeAPIUnitTest(base.DbTestCase): mock_rebuild.assert_not_called() @mock.patch.object(engine_rpcapi.EngineAPI, 'rebuild_server') - def test_rebuild_locked_server_with_admin(self, mock_rebuild): + @mock.patch('mogan.engine.api.API._get_image') + def test_rebuild_locked_server_with_admin(self, mock_rebuild, + mock_get_image): fake_server = db_utils.get_test_server( user_id=self.user_id, project_id=self.project_id, locked=True, locked_by='owner') fake_server_obj = self._create_fake_server_obj(fake_server) admin_context = context.get_admin_context() + mock_get_image.side_effect = None self.engine_api.rebuild(admin_context, fake_server_obj) self.assertTrue(mock_rebuild.called) @mock.patch.object(engine_rpcapi.EngineAPI, 'rebuild_server') - def test_rebuild_server(self, mock_rebuild): + @mock.patch('mogan.engine.api.API._get_image') + def test_rebuild_server(self, mock_rebuild, mock_get_image): fake_server = db_utils.get_test_server( user_id=self.user_id, project_id=self.project_id) fake_server_obj = self._create_fake_server_obj(fake_server) + mock_get_image.side_effect = None self.engine_api.rebuild(self.context, fake_server_obj) + self.assertTrue(mock_get_image.called) + self.assertTrue(mock_rebuild.called) + + @mock.patch.object(engine_rpcapi.EngineAPI, 'rebuild_server') + @mock.patch('mogan.engine.api.API._get_image') + def test_rebuild_server_with_new_image(self, mock_rebuild, mock_get_image): + fake_server = db_utils.get_test_server( + user_id=self.user_id, project_id=self.project_id) + fake_server_obj = self._create_fake_server_obj(fake_server) + mock_get_image.side_effect = None + image_uuid = 'fake-uuid' + + self.engine_api.rebuild(self.context, fake_server_obj, image_uuid) + self.assertTrue(mock_get_image.called) self.assertTrue(mock_rebuild.called) @mock.patch.object(engine_rpcapi.EngineAPI, 'detach_interface')