|
41 | 41 | admin |
42 | 42 |
|
43 | 43 | from nose import SkipTest |
44 | | -from nose.tools import assert_not_equal, assert_equal, assert_in, assert_true |
| 44 | +from nose.tools import assert_not_equal, assert_equal, assert_in, assert_not_in, assert_true |
45 | 45 | import boto.s3.tagging |
46 | 46 |
|
47 | 47 | # configure logging for the tests module |
@@ -5429,3 +5429,208 @@ def test_connection_caching(): |
5429 | 5429 | conn.delete_bucket(bucket_name) |
5430 | 5430 | receiver_1.close(task_1) |
5431 | 5431 | receiver_2.close(task_2) |
| 5432 | + |
| 5433 | + |
| 5434 | +@attr("http_test") |
| 5435 | +def test_topic_migration_to_an_account(): |
| 5436 | + """test the topic migration procedure described at |
| 5437 | + https://docs.ceph.com/en/latest/radosgw/account/#migrate-an-existing-user-into-an-account |
| 5438 | + """ |
| 5439 | + try: |
| 5440 | + # create an http server for notification delivery |
| 5441 | + host = get_ip() |
| 5442 | + port = random.randint(10000, 20000) |
| 5443 | + http_server = HTTPServerWithEvents((host, port)) |
| 5444 | + |
| 5445 | + # start with two non-account users |
| 5446 | + # create a new user for "user1" which is going to be migrated to an account |
| 5447 | + user1_conn, user1_arn = another_user() |
| 5448 | + user1_id = user1_arn.split("/")[1] |
| 5449 | + user2_conn = connection() |
| 5450 | + log.info( |
| 5451 | + f"two non-account users with acckeys user1 {user1_conn.access_key} and user2 {user2_conn.access_key}" |
| 5452 | + ) |
| 5453 | + |
| 5454 | + # create a bucket per user |
| 5455 | + user1_bucket_name = gen_bucket_name() |
| 5456 | + user2_bucket_name = gen_bucket_name() |
| 5457 | + user1_bucket = user1_conn.create_bucket(user1_bucket_name) |
| 5458 | + user2_bucket = user2_conn.create_bucket(user2_bucket_name) |
| 5459 | + log.info( |
| 5460 | + f"one bucket per user {user1_conn.access_key}: {user1_bucket_name} and {user2_conn.access_key}: {user2_bucket_name}" |
| 5461 | + ) |
| 5462 | + |
| 5463 | + # create an S3 topic owned by the first user |
| 5464 | + topic_name = user1_bucket_name + TOPIC_SUFFIX |
| 5465 | + zonegroup = get_config_zonegroup() |
| 5466 | + endpoint_address = "http://" + host + ":" + str(port) |
| 5467 | + endpoint_args = "push-endpoint=" + endpoint_address + "&persistent=true" |
| 5468 | + expected_topic_arn = "arn:aws:sns:" + zonegroup + "::" + topic_name |
| 5469 | + topic_conf = PSTopicS3( |
| 5470 | + user1_conn, topic_name, zonegroup, endpoint_args=endpoint_args |
| 5471 | + ) |
| 5472 | + topic_arn = topic_conf.set_config() |
| 5473 | + assert_equal(topic_arn, expected_topic_arn) |
| 5474 | + log.info( |
| 5475 | + f"{user1_conn.access_key} created the topic {topic_arn} with args {endpoint_args}" |
| 5476 | + ) |
| 5477 | + |
| 5478 | + # both buckets subscribe to the same and only topic using the same notification id |
| 5479 | + notification_name = user1_bucket_name + NOTIFICATION_SUFFIX |
| 5480 | + topic_conf_list = [ |
| 5481 | + { |
| 5482 | + "Id": notification_name, |
| 5483 | + "TopicArn": topic_arn, |
| 5484 | + "Events": ["s3:ObjectCreated:*"], |
| 5485 | + } |
| 5486 | + ] |
| 5487 | + s3_notification_conf1 = PSNotificationS3( |
| 5488 | + user1_conn, user1_bucket_name, topic_conf_list |
| 5489 | + ) |
| 5490 | + s3_notification_conf2 = PSNotificationS3( |
| 5491 | + user2_conn, user2_bucket_name, topic_conf_list |
| 5492 | + ) |
| 5493 | + _, status = s3_notification_conf1.set_config() |
| 5494 | + assert_equal(status / 100, 2) |
| 5495 | + _, status = s3_notification_conf2.set_config() |
| 5496 | + assert_equal(status / 100, 2) |
| 5497 | + # verify both buckets are subscribed to the topic |
| 5498 | + rgw_topic_entry = [ |
| 5499 | + t for t in list_topics()["topics"] if t["name"] == topic_name |
| 5500 | + ] |
| 5501 | + assert_equal(len(rgw_topic_entry), 1) |
| 5502 | + subscribed_buckets = rgw_topic_entry[0]["subscribed_buckets"] |
| 5503 | + assert_equal(len(subscribed_buckets), 2) |
| 5504 | + assert_in(user1_bucket_name, subscribed_buckets) |
| 5505 | + assert_in(user2_bucket_name, subscribed_buckets) |
| 5506 | + log.info( |
| 5507 | + "buckets {user1_bucket_name} and {user2_bucket_name} are subscribed to {topic_arn}" |
| 5508 | + ) |
| 5509 | + |
| 5510 | + # move user1 to an account |
| 5511 | + account_id = "RGW98765432101234567" |
| 5512 | + cmd = ["account", "create", "--account-id", account_id] |
| 5513 | + _, rc = admin(cmd, get_config_cluster()) |
| 5514 | + assert rc == 0, f"failed to create {account_id}: {rc}" |
| 5515 | + cmd = [ |
| 5516 | + "user", |
| 5517 | + "modify", |
| 5518 | + "--uid", |
| 5519 | + user1_id, |
| 5520 | + "--account-id", |
| 5521 | + account_id, |
| 5522 | + "--account-root", |
| 5523 | + ] |
| 5524 | + _, rc = admin(cmd, get_config_cluster()) |
| 5525 | + assert rc == 0, f"failed to modify {user1_id}: {rc}" |
| 5526 | + log.info( |
| 5527 | + f"{user1_conn.access_key}/{user1_id} is migrated to account {account_id} as root user" |
| 5528 | + ) |
| 5529 | + |
| 5530 | + # verify the topic is functional |
| 5531 | + user1_bucket.new_key("user1obj1").set_contents_from_string("object content") |
| 5532 | + user2_bucket.new_key("user2obj1").set_contents_from_string("object content") |
| 5533 | + wait_for_queue_to_drain(topic_name, http_port=port) |
| 5534 | + http_server.verify_s3_events( |
| 5535 | + list(user1_bucket.list()) + list(user2_bucket.list()), exact_match=True |
| 5536 | + ) |
| 5537 | + |
| 5538 | + # create a new account topic with the same name as the existing topic |
| 5539 | + # note that the expected arn now contains the account ID |
| 5540 | + expected_topic_arn = ( |
| 5541 | + "arn:aws:sns:" + zonegroup + ":" + account_id + ":" + topic_name |
| 5542 | + ) |
| 5543 | + topic_conf = PSTopicS3( |
| 5544 | + user1_conn, topic_name, zonegroup, endpoint_args=endpoint_args |
| 5545 | + ) |
| 5546 | + account_topic_arn = topic_conf.set_config() |
| 5547 | + assert_equal(account_topic_arn, expected_topic_arn) |
| 5548 | + log.info( |
| 5549 | + f"{user1_conn.access_key} created the account topic {account_topic_arn} with args {endpoint_args}" |
| 5550 | + ) |
| 5551 | + |
| 5552 | + # verify that the old topic is still functional |
| 5553 | + user1_bucket.new_key("user1obj1").set_contents_from_string("object content") |
| 5554 | + user2_bucket.new_key("user2obj1").set_contents_from_string("object content") |
| 5555 | + wait_for_queue_to_drain(topic_name, http_port=port) |
| 5556 | + # wait_for_queue_to_drain(topic_name, tenant=account_id, http_port=port) |
| 5557 | + http_server.verify_s3_events( |
| 5558 | + list(user1_bucket.list()) + list(user2_bucket.list()), exact_match=True |
| 5559 | + ) |
| 5560 | + |
| 5561 | + # change user1 bucket's subscription to the account topic - using the same notification ID but with the new account_topic_arn |
| 5562 | + topic_conf_list = [ |
| 5563 | + { |
| 5564 | + "Id": notification_name, |
| 5565 | + "TopicArn": account_topic_arn, |
| 5566 | + "Events": ["s3:ObjectCreated:*"], |
| 5567 | + } |
| 5568 | + ] |
| 5569 | + s3_notification_conf1 = PSNotificationS3( |
| 5570 | + user1_conn, user1_bucket_name, topic_conf_list |
| 5571 | + ) |
| 5572 | + _, status = s3_notification_conf1.set_config() |
| 5573 | + assert_equal(status / 100, 2) |
| 5574 | + rgw_topic_entry = [ |
| 5575 | + t |
| 5576 | + for t in list_topics(tenant=account_id)["topics"] |
| 5577 | + if t["name"] == topic_name |
| 5578 | + ] |
| 5579 | + assert_equal(len(rgw_topic_entry), 1) |
| 5580 | + subscribed_buckets = rgw_topic_entry[0]["subscribed_buckets"] |
| 5581 | + assert_equal(len(subscribed_buckets), 1) |
| 5582 | + assert_in(user1_bucket_name, subscribed_buckets) |
| 5583 | + assert_not_in(user2_bucket_name, subscribed_buckets) |
| 5584 | + |
| 5585 | + # verify both topics are functional at the same time with no duplicate notifications |
| 5586 | + user1_bucket.new_key("user1obj1").set_contents_from_string("object content") |
| 5587 | + user2_bucket.new_key("user2obj1").set_contents_from_string("object content") |
| 5588 | + wait_for_queue_to_drain(topic_name, http_port=port) |
| 5589 | + wait_for_queue_to_drain(topic_name, tenant=account_id, http_port=port) |
| 5590 | + http_server.verify_s3_events( |
| 5591 | + list(user1_bucket.list()) + list(user2_bucket.list()), exact_match=True |
| 5592 | + ) |
| 5593 | + |
| 5594 | + # also change user2 bucket's subscription to the account topic |
| 5595 | + s3_notification_conf2 = PSNotificationS3( |
| 5596 | + user2_conn, user2_bucket_name, topic_conf_list |
| 5597 | + ) |
| 5598 | + _, status = s3_notification_conf2.set_config() |
| 5599 | + assert_equal(status / 100, 2) |
| 5600 | + # remove old topic |
| 5601 | + # note that, although account topic has the same name, it has to be scoped by account/tenant id to be removed |
| 5602 | + # so below command will only remove the old topic |
| 5603 | + _, rc = admin(["topic", "rm", "--topic", topic_name], get_config_cluster()) |
| 5604 | + assert_equal(rc, 0) |
| 5605 | + |
| 5606 | + # now verify account topic serves both buckets |
| 5607 | + rgw_topic_entry = [ |
| 5608 | + t |
| 5609 | + for t in list_topics(tenant=account_id)["topics"] |
| 5610 | + if t["name"] == topic_name |
| 5611 | + ] |
| 5612 | + assert_equal(len(rgw_topic_entry), 1) |
| 5613 | + subscribed_buckets = rgw_topic_entry[0]["subscribed_buckets"] |
| 5614 | + assert_equal(len(subscribed_buckets), 2) |
| 5615 | + assert_in(user1_bucket_name, subscribed_buckets) |
| 5616 | + assert_in(user2_bucket_name, subscribed_buckets) |
| 5617 | + |
| 5618 | + # finally, make sure that notifications are going thru via the new account topic |
| 5619 | + user1_bucket.new_key("user1obj1").set_contents_from_string("object content") |
| 5620 | + user2_bucket.new_key("user2obj1").set_contents_from_string("object content") |
| 5621 | + wait_for_queue_to_drain(topic_name, tenant=account_id, http_port=port) |
| 5622 | + http_server.verify_s3_events( |
| 5623 | + list(user1_bucket.list()) + list(user2_bucket.list()), exact_match=True |
| 5624 | + ) |
| 5625 | + log.info("topic migration test has completed successfully") |
| 5626 | + finally: |
| 5627 | + admin(["user", "rm", "--uid", user1_id, "--purge-data"], get_config_cluster()) |
| 5628 | + admin( |
| 5629 | + ["bucket", "rm", "--bucket", user1_bucket_name, "--purge-data"], |
| 5630 | + get_config_cluster(), |
| 5631 | + ) |
| 5632 | + admin( |
| 5633 | + ["bucket", "rm", "--bucket", user2_bucket_name, "--purge-data"], |
| 5634 | + get_config_cluster(), |
| 5635 | + ) |
| 5636 | + admin(["account", "rm", "--account-id", account_id], get_config_cluster()) |
0 commit comments