diff --git a/os_xenapi/dom0/etc/xapi.d/plugins/glance.py b/os_xenapi/dom0/etc/xapi.d/plugins/glance.py index cc68cef..9bc8ede 100644 --- a/os_xenapi/dom0/etc/xapi.d/plugins/glance.py +++ b/os_xenapi/dom0/etc/xapi.d/plugins/glance.py @@ -297,7 +297,7 @@ def _upload_tarball_by_url_v1(staging_path, image_id, glance_endpoint, conn.close() -def _update_image_meta_v2(conn, image_id, extra_headers, properties): +def _update_image_meta_v2(conn, extra_headers, properties, patch_path): # NOTE(sirp): There is some confusion around OVF. Here's a summary # of where we currently stand: # 1. OVF as a container format is misnamed. We really should be @@ -321,8 +321,8 @@ def _update_image_meta_v2(conn, image_id, extra_headers, properties): "op": "add"} body.append(prop) body = json.dumps(body) - conn.request('PATCH', '/v2/images/%s' % image_id, - body=body, headers=headers) + + conn.request('PATCH', patch_path, body=body, headers=headers) resp = conn.getresponse() resp.read() @@ -364,9 +364,17 @@ def _upload_tarball_by_url_v2(staging_path, image_id, glance_endpoint, raise RetryableError(error) try: - _update_image_meta_v2(conn, image_id, extra_headers, properties) + mgt_url = "%(glance_endpoint)s/v2/images/%(image_id)s" % { + 'glance_endpoint': glance_endpoint, + 'image_id': image_id} + mgt_parts = urlparse(mgt_url) + mgt_path = mgt_parts[2] - validate_image_status_before_upload_v2(conn, url, extra_headers) + _update_image_meta_v2(conn, image_id, extra_headers, properties, + mgt_path) + + validate_image_status_before_upload_v2(conn, url, extra_headers, + mgt_path) try: conn.connect() @@ -537,7 +545,8 @@ def validate_image_status_before_upload_v1(conn, url, extra_headers): 'image_status': image_status}) -def validate_image_status_before_upload_v2(conn, url, extra_headers): +def validate_image_status_before_upload_v2(conn, url, extra_headers, + get_path): try: parts = urlparse(url) path = parts[2] @@ -548,8 +557,7 @@ def validate_image_status_before_upload_v2(conn, url, extra_headers): # it is not 'active' and send back a 409. Hence, the data will be # unnecessarily buffered by Glance. This wastes time and bandwidth. # LP bug #1202785 - - conn.request('GET', '/v2/images/%s' % image_id, headers=extra_headers) + conn.request('GET', get_path, headers=extra_headers) get_resp = conn.getresponse() except Exception, error: # noqa logging.exception('Failed to GET the image %(image_id)s while ' diff --git a/os_xenapi/tests/plugins/test_glance.py b/os_xenapi/tests/plugins/test_glance.py index cc51b71..59d32c4 100644 --- a/os_xenapi/tests/plugins/test_glance.py +++ b/os_xenapi/tests/plugins/test_glance.py @@ -333,6 +333,7 @@ class GlanceTestCase(plugin_test.PluginTestBase): expected_url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { 'glance_endpoint': fake_endpoint, 'image_id': 'fake_image_id'} + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' self.glance._upload_tarball_by_url_v2( 'fake_staging_path', 'fake_image_id', fake_endpoint, @@ -341,7 +342,8 @@ class GlanceTestCase(plugin_test.PluginTestBase): self.assertTrue(mock_HTTPConn.called) mock_validate_image.assert_called_with(fake_conn, expected_url, - fake_extra_headers) + fake_extra_headers, + expected_wsgi_path) self.assertTrue(mock_update_image_meta.called) self.assertTrue(mock_create_tarball.called) self.assertTrue( @@ -369,6 +371,7 @@ class GlanceTestCase(plugin_test.PluginTestBase): expected_url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { 'glance_endpoint': fake_endpoint, 'image_id': 'fake_image_id'} + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' self.glance._upload_tarball_by_url_v2( 'fake_staging_path', 'fake_image_id', fake_endpoint, @@ -378,12 +381,89 @@ class GlanceTestCase(plugin_test.PluginTestBase): self.assertTrue(mock_update_image_meta.called) mock_validate_image.assert_called_with(fake_conn, expected_url, - fake_extra_headers) + fake_extra_headers, + expected_wsgi_path) self.assertTrue(mock_create_tarball.called) self.assertTrue( mock_HTTPSConn.return_value.getresponse.called) self.assertFalse(mock_check_resp_status.called) + def test_upload_tarball_by_url_v2_with_api_endpoint(self): + fake_conn = mock.Mock() + mock_Conn = self.mock_patch_object( + self.glance, '_create_connection', fake_conn) + mock_validate_image = self.mock_patch_object( + self.glance, 'validate_image_status_before_upload_v2') + mock_create_tarball = self.mock_patch_object( + self.glance.utils, 'create_tarball') + mock_check_resp_status = self.mock_patch_object( + self.glance, 'check_resp_status_and_retry') + mock_update_image_meta = self.mock_patch_object( + self.glance, '_update_image_meta_v2') + self.glance._create_connection().getresponse = mock.Mock() + self.glance._create_connection().getresponse().status = \ + httplib.NO_CONTENT + fake_extra_headers = {} + fake_properties = {} + fake_endpoint = 'https://fake_netloc:fake_port' + expected_url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { + 'glance_endpoint': fake_endpoint, + 'image_id': 'fake_image_id'} + expected_api_path = '/v2/images/%s' % 'fake_image_id' + + self.glance._upload_tarball_by_url_v2( + 'fake_staging_path', 'fake_image_id', fake_endpoint, + fake_extra_headers, fake_properties) + + self.assertTrue(mock_Conn.called) + self.assertTrue(mock_update_image_meta.called) + mock_validate_image.assert_called_with(fake_conn, + expected_url, + fake_extra_headers, + expected_api_path) + self.assertTrue(mock_create_tarball.called) + self.assertTrue( + mock_Conn.return_value.getresponse.called) + self.assertFalse(mock_check_resp_status.called) + + def test_upload_tarball_by_url_v2_with_wsgi_endpoint(self): + fake_conn = mock.Mock() + mock_Conn = self.mock_patch_object( + self.glance, '_create_connection', fake_conn) + mock_validate_image = self.mock_patch_object( + self.glance, 'validate_image_status_before_upload_v2') + mock_create_tarball = self.mock_patch_object( + self.glance.utils, 'create_tarball') + mock_check_resp_status = self.mock_patch_object( + self.glance, 'check_resp_status_and_retry') + mock_update_image_meta = self.mock_patch_object( + self.glance, '_update_image_meta_v2') + self.glance._create_connection().getresponse = mock.Mock() + self.glance._create_connection().getresponse().status = \ + httplib.NO_CONTENT + fake_extra_headers = {} + fake_properties = {} + fake_endpoint = 'https://fake_netloc/fake_path' + expected_url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { + 'glance_endpoint': fake_endpoint, + 'image_id': 'fake_image_id'} + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' + + self.glance._upload_tarball_by_url_v2( + 'fake_staging_path', 'fake_image_id', fake_endpoint, + fake_extra_headers, fake_properties) + + self.assertTrue(mock_Conn.called) + self.assertTrue(mock_update_image_meta.called) + mock_validate_image.assert_called_with(fake_conn, + expected_url, + fake_extra_headers, + expected_wsgi_path) + self.assertTrue(mock_create_tarball.called) + self.assertTrue( + mock_Conn.return_value.getresponse.called) + self.assertFalse(mock_check_resp_status.called) + def test_upload_tarball_by_url_https_failed_retry_v2(self): fake_conn = mock.Mock() mock_HTTPSConn = self.mock_patch_object( @@ -405,6 +485,7 @@ class GlanceTestCase(plugin_test.PluginTestBase): expected_url = "%(glance_endpoint)s/v2/images/%(image_id)s/file" % { 'glance_endpoint': fake_endpoint, 'image_id': 'fake_image_id'} + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' self.glance._upload_tarball_by_url_v2( 'fake_staging_path', 'fake_image_id', fake_endpoint, @@ -414,13 +495,14 @@ class GlanceTestCase(plugin_test.PluginTestBase): self.assertTrue(mock_update_image_meta.called) mock_validate_image.assert_called_with(fake_conn, expected_url, - fake_extra_headers) + fake_extra_headers, + expected_wsgi_path) self.assertTrue(mock_create_tarball.called) self.assertTrue( mock_HTTPSConn.return_value.getresponse.called) self.assertTrue(mock_check_resp_status.called) - def test_update_image_meta_ok_v2(self): + def test_update_image_meta_ok_v2_using_api_service(self): fake_conn = mock.Mock() fake_extra_headers = {'fake_type': 'fake_content'} fake_properties = {'fake_path': True} @@ -438,15 +520,45 @@ class GlanceTestCase(plugin_test.PluginTestBase): fake_headers.update(**fake_extra_headers) fake_conn.getresponse.return_value = mock.Mock() fake_conn.getresponse().status = httplib.OK + expected_api_path = '/v2/images/%s' % 'fake_image_id' - self.glance._update_image_meta_v2(fake_conn, 'fake_image_id', - fake_extra_headers, fake_properties) + self.glance._update_image_meta_v2(fake_conn, fake_extra_headers, + fake_properties, expected_api_path) fake_conn.request.assert_called_with('PATCH', '/v2/images/%s' % 'fake_image_id', body=fake_body_json, headers=fake_headers) fake_conn.getresponse.assert_called() + def test_update_image_meta_ok_v2_using_uwsgi_service(self): + fake_conn = mock.Mock() + fake_extra_headers = {'fake_type': 'fake_content'} + fake_properties = {'fake_path': True} + new_fake_properties = {'path': '/fake-path', + 'value': "True", + 'op': 'add'} + fake_body = [ + {"path": "/container_format", "value": "ovf", "op": "add"}, + {"path": "/disk_format", "value": "vhd", "op": "add"}, + {"path": "/visibility", "value": "private", "op": "add"}] + fake_body.append(new_fake_properties) + fake_body_json = json.dumps(fake_body) + fake_headers = { + 'Content-Type': 'application/openstack-images-v2.1-json-patch'} + fake_headers.update(**fake_extra_headers) + fake_conn.getresponse.return_value = mock.Mock() + fake_conn.getresponse().status = httplib.OK + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' + + self.glance._update_image_meta_v2(fake_conn, fake_extra_headers, + fake_properties, expected_wsgi_path) + fake_conn.request.assert_called_with('PATCH', + '/fake_path/v2/images/%s' % + 'fake_image_id', + body=fake_body_json, + headers=fake_headers) + fake_conn.getresponse.assert_called() + def test_check_resp_status_and_retry_plugin_error(self): mock_resp_badrequest = mock.Mock() mock_resp_badrequest.status = httplib.BAD_REQUEST @@ -579,7 +691,30 @@ class GlanceTestCase(plugin_test.PluginTestBase): mock_head_resp, fake_image_id, fake_url) mock_conn.request.assert_called_once() - def test_validate_image_status_before_upload_ok_v2(self): + def test_validate_image_status_before_upload_ok_v2_using_api_service(self): + mock_conn = mock.Mock() + fake_url = 'http://fake_host:fake_port/fake_path/fake_image_id' + mock_check_resp_status_and_retry = self.mock_patch_object( + self.glance, 'check_resp_status_and_retry') + mock_head_resp = mock.Mock() + mock_head_resp.status = httplib.OK + mock_head_resp.read.return_value = '{"status": "queued"}' + mock_conn.getresponse.return_value = mock_head_resp + fake_extra_headers = mock.Mock() + expected_api_path = '/v2/images/%s' % 'fake_image_id' + + self.glance.validate_image_status_before_upload_v2( + mock_conn, fake_url, fake_extra_headers, expected_api_path) + + self.assertTrue(mock_conn.getresponse.called) + self.assertEqual( + mock_head_resp.read.call_count, 2) + self.assertFalse(mock_check_resp_status_and_retry.called) + mock_conn.request.assert_called_with('GET', + '/v2/images/fake_image_id', + headers=fake_extra_headers) + + def test_validate_image_status_before_upload_ok_v2_using_uwsgi(self): mock_conn = mock.Mock() fake_url = 'http://fake_host/fake_path/fake_image_id' mock_check_resp_status_and_retry = self.mock_patch_object( @@ -589,14 +724,19 @@ class GlanceTestCase(plugin_test.PluginTestBase): mock_head_resp.read.return_value = '{"status": "queued"}' mock_conn.getresponse.return_value = mock_head_resp + fake_extra_headers = mock.Mock() + fake_patch_path = 'fake_patch_path' + self.glance.validate_image_status_before_upload_v2( - mock_conn, fake_url, extra_headers=mock.Mock()) + mock_conn, fake_url, fake_extra_headers, fake_patch_path) self.assertTrue(mock_conn.getresponse.called) self.assertEqual( mock_head_resp.read.call_count, 2) self.assertFalse(mock_check_resp_status_and_retry.called) - mock_conn.request.assert_called_once() + mock_conn.request.assert_called_with('GET', + 'fake_patch_path', + headers=fake_extra_headers) def test_validate_image_status_before_upload_get_image_failed_v2(self): mock_conn = mock.Mock() @@ -605,10 +745,11 @@ class GlanceTestCase(plugin_test.PluginTestBase): mock_head_resp = mock.Mock() mock_head_resp.status = httplib.OK mock_conn.getresponse.return_value = mock_head_resp + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' self.assertRaises(self.glance.RetryableError, self.glance.validate_image_status_before_upload_v2, - mock_conn, fake_url, extra_headers=mock.Mock()) + mock_conn, fake_url, mock.Mock(), expected_wsgi_path) mock_conn.request.assert_called_once() mock_head_resp.read.assert_not_called() mock_conn.getresponse.assert_not_called() @@ -620,9 +761,10 @@ class GlanceTestCase(plugin_test.PluginTestBase): mock_head_resp = mock.Mock() mock_head_resp.status = httplib.BAD_REQUEST mock_conn.getresponse.return_value = mock_head_resp + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' self.glance.validate_image_status_before_upload_v2( - mock_conn, fake_url, extra_headers=mock.Mock()) + mock_conn, fake_url, mock.Mock(), expected_wsgi_path) mock_conn.request.assert_called_once() mock_conn.getresponse.assert_called_once() mock_head_resp.read.assert_called_once() @@ -635,10 +777,11 @@ class GlanceTestCase(plugin_test.PluginTestBase): mock_head_resp.status = httplib.OK mock_head_resp.read.return_value = '{"status": "not-queued"}' mock_conn.getresponse.return_value = mock_head_resp + expected_wsgi_path = '/fake_path/v2/images/%s' % 'fake_image_id' self.assertRaises(self.glance.PluginError, self.glance.validate_image_status_before_upload_v2, - mock_conn, fake_url, extra_headers=mock.Mock()) + mock_conn, fake_url, mock.Mock(), expected_wsgi_path) mock_conn.request.assert_called_once() mock_head_resp.read.assert_called_once()