|
13 | 13 | from six import BytesIO |
14 | 14 | import hashlib |
15 | 15 | import tempfile |
| 16 | +from cadcutils import exceptions |
16 | 17 |
|
17 | 18 |
|
18 | 19 | # The following is a temporary workaround for Python issue 25532 |
@@ -505,120 +506,104 @@ def is_remote_file(uri): |
505 | 506 | test_client.get_node_url = get_node_url_mock |
506 | 507 | get_node_mock.reset_mock() |
507 | 508 | response.iter_content.return_value = BytesIO(file_content) |
508 | | - headers.get.return_value = None |
509 | 509 | test_client.copy(vospaceLocation, osLocation, head=True) |
510 | 510 | get_node_url_mock.assert_called_once_with(vospaceLocation, |
511 | 511 | method='GET', |
512 | 512 | cutout=None, view='header') |
513 | 513 |
|
514 | | - # patch sleep to stop the test from sleeping and slowing down execution |
515 | | - @patch('vos.vos.time.sleep', MagicMock(), create=True) |
516 | | - def test_transfer_error(self): |
517 | | - session = Mock() |
518 | | - conn_mock = MagicMock(spec=Connection) |
519 | | - conn_mock.session.return_value = session |
520 | | - end_point_mock = Mock(session=session) |
521 | | - |
522 | | - vospace_url = 'https://somevospace.server/vospace' |
523 | | - |
524 | | - session.get.side_effect = [Mock(text='COMPLETED'), |
525 | | - Mock(text='COMPLETED')] |
526 | | - test_transfer = vos.Transfer(end_point_mock) |
527 | | - |
528 | | - # job successfully completed |
529 | | - self.assertFalse(test_transfer.get_transfer_error( |
530 | | - vospace_url + '/results/transferDetails', 'vos://vospace')) |
531 | | - session.get.assert_called_with(vospace_url + '/phase', |
532 | | - allow_redirects=True) |
533 | | - |
534 | | - # job suspended |
535 | | - session.reset_mock() |
536 | | - session.get.side_effect = [Mock(text='COMPLETED'), |
537 | | - Mock(text='ABORTED')] |
538 | | - with self.assertRaises(OSError): |
539 | | - test_transfer.get_transfer_error( |
540 | | - vospace_url + '/results/transferDetails', 'vos://vospace') |
541 | | - # check arguments for session.get calls |
542 | | - self.assertEqual( |
543 | | - [call(vospace_url + '/phase', allow_redirects=True), |
544 | | - call(vospace_url + '/phase', allow_redirects=True)], |
545 | | - session.get.call_args_list) |
546 | | - |
547 | | - # job encountered an internal error |
548 | | - session.reset_mock() |
549 | | - session.get.side_effect = [Mock(text='COMPLETED'), |
550 | | - Mock(text='ERROR'), |
551 | | - Mock(text='InternalFault')] |
552 | | - with self.assertRaises(OSError): |
553 | | - test_transfer.get_transfer_error( |
554 | | - vospace_url + '/results/transferDetails', 'vos://vospace') |
555 | | - self.assertEqual([call(vospace_url + '/phase', allow_redirects=True), |
556 | | - call(vospace_url + '/phase', allow_redirects=True), |
557 | | - call(vospace_url + '/error')], |
558 | | - session.get.call_args_list) |
559 | | - |
560 | | - # job encountered an unsupported link error |
561 | | - session.reset_mock() |
562 | | - link_file = 'testlink.fits' |
563 | | - session.get.side_effect = [Mock(text='COMPLETED'), |
564 | | - Mock(text='ERROR'), |
565 | | - Mock( |
566 | | - text="Unsupported link target: " + |
567 | | - link_file)] |
568 | | - self.assertEqual(link_file, test_transfer.get_transfer_error( |
569 | | - vospace_url + '/results/transferDetails', 'vos://vospace')) |
570 | | - self.assertEqual([call(vospace_url + '/phase', allow_redirects=True), |
571 | | - call(vospace_url + '/phase', allow_redirects=True), |
572 | | - call(vospace_url + '/error')], |
573 | | - session.get.call_args_list) |
574 | | - |
575 | | - def test_transfer(self): |
576 | | - session = Mock() |
577 | | - redirect_response = Mock() |
578 | | - redirect_response.status_code = 303 |
579 | | - redirect_response.headers = \ |
580 | | - {'Location': 'https://transfer.host/transfer'} |
581 | | - response = Mock() |
582 | | - response.status_code = 200 |
583 | | - response.text = ( |
584 | | - '<?xml version="1.0" encoding="UTF-8"?>' |
585 | | - '<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" ' |
586 | | - 'version="2.1">' |
587 | | - '<vos:target>vos://some.host~vault/abc</vos:target>' |
588 | | - '<vos:direction>pullFromVoSpace</vos:direction>' |
589 | | - '<vos:protocol uri="ivo://ivoa.net/vospace/core#httpsget">' |
590 | | - '<vos:endpoint>https://transfer.host/transfer/abc</vos:endpoint>' |
591 | | - '<vos:securityMethod ' |
592 | | - 'uri="ivo://ivoa.net/sso#tls-with-certificate" />' |
593 | | - '</vos:protocol>' |
594 | | - '<vos:keepBytes>true</vos:keepBytes>' |
595 | | - '</vos:transfer>') |
596 | | - session.post.return_value = redirect_response |
597 | | - session.get.return_value = response |
598 | | - conn_mock = MagicMock(spec=Connection) |
599 | | - conn_mock.session.return_value = session |
600 | | - end_point_mock = Mock(session=session) |
601 | | - test_transfer = vos.Transfer(end_point_mock) |
602 | | - test_transfer.get_transfer_error = Mock() # not transfer error |
603 | | - protocols = test_transfer.transfer( |
604 | | - 'https://some.host/service', 'vos://abc', 'pullFromVoSpace') |
605 | | - assert protocols == ['https://transfer.host/transfer/abc'] |
606 | | - |
607 | | - session.reset_mock() |
608 | | - session.post.return_value = Mock(status_code=404) |
609 | | - with self.assertRaises(OSError) as e: |
610 | | - test_transfer.transfer( |
611 | | - 'https://some.host/service', 'vos://abc', |
612 | | - 'pullFromVoSpace') |
613 | | - assert 'File not found: vos://abc' == str(e) |
614 | | - |
615 | | - session.reset_mock() |
616 | | - session.post.return_value = Mock(status_code=500) |
617 | | - with self.assertRaises(OSError) as e: |
618 | | - test_transfer.transfer( |
619 | | - 'https://some.host/service', 'vos://abc', |
620 | | - 'pullFromVoSpace') |
621 | | - assert 'Failed to get transfer service response.' == str(e) |
| 514 | + # test GET intermittent exceptions on both URLs |
| 515 | + props.get.side_effect = md5sum |
| 516 | + get_node_url_mock = Mock( |
| 517 | + return_value=['http://cadc1.ca/test', 'http://cadc2.ca/test']) |
| 518 | + test_client.get_node_url = get_node_url_mock |
| 519 | + get_node_mock.reset_mock() |
| 520 | + response.iter_content.return_value = BytesIO(file_content) |
| 521 | + headers.get.return_value = None |
| 522 | + session.get.reset_mock() |
| 523 | + session.get.side_effect = \ |
| 524 | + [exceptions.TransferException()] * 2 * vos.MAX_INTERMTTENT_RETRIES |
| 525 | + with pytest.raises(OSError): |
| 526 | + test_client.copy(vospaceLocation, osLocation, head=True) |
| 527 | + assert session.get.call_count == 2 * vos.MAX_INTERMTTENT_RETRIES |
| 528 | + |
| 529 | + # test GET Transfer error on one URL and a "permanent" one on the other |
| 530 | + props.get.side_effect = md5sum |
| 531 | + get_node_url_mock = Mock( |
| 532 | + return_value=['http://cadc1.ca/test', 'http://cadc2.ca/test']) |
| 533 | + test_client.get_node_url = get_node_url_mock |
| 534 | + get_node_mock.reset_mock() |
| 535 | + response.iter_content.return_value = BytesIO(file_content) |
| 536 | + headers.get.return_value = None |
| 537 | + session.get.reset_mock() |
| 538 | + session.get.side_effect = [exceptions.TransferException(), |
| 539 | + exceptions.HttpException(), |
| 540 | + exceptions.TransferException(), |
| 541 | + exceptions.TransferException()] |
| 542 | + with pytest.raises(OSError): |
| 543 | + test_client.copy(vospaceLocation, osLocation, head=True) |
| 544 | + assert session.get.call_count == vos.MAX_INTERMTTENT_RETRIES + 1 |
| 545 | + |
| 546 | + # test GET both "permanent" errors |
| 547 | + props.get.side_effect = md5sum |
| 548 | + get_node_url_mock = Mock( |
| 549 | + return_value=['http://cadc1.ca/test', 'http://cadc2.ca/test']) |
| 550 | + test_client.get_node_url = get_node_url_mock |
| 551 | + get_node_mock.reset_mock() |
| 552 | + response.iter_content.return_value = BytesIO(file_content) |
| 553 | + headers.get.return_value = None |
| 554 | + session.get.reset_mock() |
| 555 | + session.get.side_effect = [exceptions.HttpException(), |
| 556 | + exceptions.HttpException()] |
| 557 | + with pytest.raises(OSError): |
| 558 | + test_client.copy(vospaceLocation, osLocation, head=True) |
| 559 | + assert session.get.call_count == 2 |
| 560 | + |
| 561 | + # test PUT intermittent exceptions on both URLs |
| 562 | + props.get.side_effect = md5sum |
| 563 | + get_node_url_mock = Mock( |
| 564 | + return_value=['http://cadc1.ca/test', 'http://cadc2.ca/test']) |
| 565 | + test_client.get_node_url = get_node_url_mock |
| 566 | + get_node_mock.reset_mock() |
| 567 | + response.iter_content.return_value = BytesIO(file_content) |
| 568 | + headers.get.return_value = None |
| 569 | + session.put.reset_mock() |
| 570 | + session.put.side_effect = \ |
| 571 | + [exceptions.TransferException()] * 2 * vos.MAX_INTERMTTENT_RETRIES |
| 572 | + with pytest.raises(OSError): |
| 573 | + test_client.copy(osLocation, vospaceLocation, head=True) |
| 574 | + assert session.put.call_count == 2 * vos.MAX_INTERMTTENT_RETRIES |
| 575 | + |
| 576 | + # test GET Transfer error on one URL and a "permanent" one on the other |
| 577 | + props.get.side_effect = md5sum |
| 578 | + get_node_url_mock = Mock( |
| 579 | + return_value=['http://cadc1.ca/test', 'http://cadc2.ca/test']) |
| 580 | + test_client.get_node_url = get_node_url_mock |
| 581 | + get_node_mock.reset_mock() |
| 582 | + response.iter_content.return_value = BytesIO(file_content) |
| 583 | + headers.get.return_value = None |
| 584 | + session.put.reset_mock() |
| 585 | + session.put.side_effect = [exceptions.TransferException(), |
| 586 | + exceptions.HttpException(), |
| 587 | + exceptions.TransferException(), |
| 588 | + exceptions.TransferException()] |
| 589 | + with pytest.raises(OSError): |
| 590 | + test_client.copy(osLocation, vospaceLocation, head=True) |
| 591 | + assert session.put.call_count == vos.MAX_INTERMTTENT_RETRIES + 1 |
| 592 | + |
| 593 | + # test GET both "permanent" errors |
| 594 | + props.get.side_effect = md5sum |
| 595 | + get_node_url_mock = Mock( |
| 596 | + return_value=['http://cadc1.ca/test', 'http://cadc2.ca/test']) |
| 597 | + test_client.get_node_url = get_node_url_mock |
| 598 | + get_node_mock.reset_mock() |
| 599 | + response.iter_content.return_value = BytesIO(file_content) |
| 600 | + headers.get.return_value = None |
| 601 | + session.put.reset_mock() |
| 602 | + session.put.side_effect = [exceptions.HttpException(), |
| 603 | + exceptions.HttpException()] |
| 604 | + with pytest.raises(OSError): |
| 605 | + test_client.copy(osLocation, vospaceLocation, head=True) |
| 606 | + assert session.put.call_count == 2 |
622 | 607 |
|
623 | 608 | def test_add_props(self): |
624 | 609 | old_node = Node(ElementTree.fromstring(NODE_XML)) |
|
0 commit comments