# Test of mult-tenancy

import json
import sys

from boto.s3.connection import S3Connection, OrdinaryCallingFormat

# XXX once we're done, break out the common code into a library module
#     See  https://github.com/ceph/ceph/pull/8646
import test_multi as t

class TestException(Exception):
    pass

#
# Create a traditional user, S3-only, global (empty) tenant
#
def test2(cluster):
    uid = "tester2"
    display_name = "'Test User 2'"
    access_key = "tester2KEY"
    s3_secret = "test3pass"
    cmd = t.build_cmd('--uid', uid,
                    '--display-name', display_name,
                    '--access-key', access_key,
                    '--secret', s3_secret,
                    "user create")
    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: user create --uid %s" % uid)

    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: user create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: user create --uid %s" % uid)
    if outj['user_id'] != uid:
        raise TestException(
            "command: user create --uid %s, returned user_id %s" %
            (uid, outj['user_id']))

#
# Create a tenantized user with --tenant foo
#
def test3(cluster):
    tid = "testx3"
    uid = "tester3"
    display_name = "Test_User_3"
    access_key = "tester3KEY"
    s3_secret = "test3pass"
    cmd = t.build_cmd(
        '--tenant', tid,
        '--uid', uid,
        '--display-name', display_name,
        '--access-key', access_key,
        '--secret', s3_secret,
        "user create")
    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: user create --uid %s" % uid)

    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: user create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: user create --uid %s" % uid)
    tid_uid = "%s$%s" % (tid, uid)
    if outj['user_id'] != tid_uid:
        raise TestException(
            "command: user create --uid %s, returned user_id %s" %
            (tid_uid, outj['user_id']))

#
# Create a tenantized user with a subuser
#
# N.B. The aim of this test is not just to create a subuser, but to create
# the key with a separate command, which does not use --tenant, but extracts
# the tenant from the subuser. No idea why we allow this. There was some kind
# of old script that did this.
#
def test4(cluster):
    tid = "testx4"
    uid = "tester4"
    subid = "test4"

    display_name = "Test_User_4"
    cmd = t.build_cmd(
        '--tenant', tid,
        '--uid', uid,
        '--display-name', display_name,
        '--subuser', '%s:%s' % (uid, subid),
        '--key-type', 'swift',
        '--access', 'full',
        "user create")
    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: user create --uid %s" % uid)

    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: user create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: user create --uid %s" % uid)
    tid_uid = "%s$%s" % (tid, uid)
    if outj['user_id'] != tid_uid:
        raise TestException(
            "command: user create --uid %s, returned user_id %s" %
            (tid_uid, outj['user_id']))

    # Note that this tests a way to identify a fully-qualified subuser
    # without --tenant and --uid. This is a historic use that we support.
    swift_secret = "test3pass"
    cmd = t.build_cmd(
        '--subuser', "'%s$%s:%s'" % (tid, uid, subid),
        '--key-type', 'swift',
        '--secret', swift_secret,
        "key create")
    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: key create --uid %s" % uid)

    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: key create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: key create --uid %s" % uid)
    tid_uid = "%s$%s" % (tid, uid)
    if outj['user_id'] != tid_uid:
        raise TestException(
            "command: key create --uid %s, returned user_id %s" %
            (tid_uid, outj['user_id']))
    # These tests easily can throw KeyError, needs a try: XXX
    skj = outj['swift_keys'][0]
    if skj['secret_key'] != swift_secret:
        raise TestException(
            "command: key create --uid %s, returned swift key %s" %
            (tid_uid, skj['secret_key']))

#
# Access the cluster, create containers in two tenants, verify it all works.
#

def test5_add_s3_key(cluster, tid, uid):
    secret = "%spass" % uid
    if tid:
        tid_uid = "%s$%s" % (tid, uid)
    else:
        tid_uid = uid

    cmd = t.build_cmd(
        '--uid', "'%s'" % (tid_uid,),
        '--access-key', uid,
        '--secret', secret,
        "key create")
    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: key create --uid %s" % uid)

    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: key create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: key create --uid %s" % uid)
    if outj['user_id'] != tid_uid:
        raise TestException(
            "command: key create --uid %s, returned user_id %s" %
            (uid, outj['user_id']))
    skj = outj['keys'][0]
    if skj['secret_key'] != secret:
        raise TestException(
            "command: key create --uid %s, returned s3 key %s" %
            (uid, skj['secret_key']))

def test5_add_swift_key(cluster, tid, uid, subid):
    secret = "%spass" % uid
    if tid:
        tid_uid = "%s$%s" % (tid, uid)
    else:
        tid_uid = uid

    cmd = t.build_cmd(
        '--subuser', "'%s:%s'" % (tid_uid, subid),
        '--key-type', 'swift',
        '--secret', secret,
        "key create")
    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: key create --uid %s" % uid)

    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: key create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: key create --uid %s" % uid)
    if outj['user_id'] != tid_uid:
        raise TestException(
            "command: key create --uid %s, returned user_id %s" %
            (uid, outj['user_id']))
    # XXX checking wrong thing here (S3 key)
    skj = outj['keys'][0]
    if skj['secret_key'] != secret:
        raise TestException(
            "command: key create --uid %s, returned s3 key %s" %
            (uid, skj['secret_key']))

def test5_make_user(cluster, tid, uid, subid):
    """
    :param tid: Tenant ID string or None for the legacy tenant
    :param uid: User ID string
    :param subid: Subuser ID, may be None for S3-only users
    """
    display_name = "'Test User %s'" % uid

    cmd = ""
    if tid:
        cmd = t.build_cmd(cmd,
            '--tenant', tid)
    cmd = t.build_cmd(cmd,
        '--uid', uid,
        '--display-name', display_name)
    if subid:
        cmd = t.build_cmd(cmd,
            '--subuser', '%s:%s' % (uid, subid),
            '--key-type', 'swift')
    cmd = t.build_cmd(cmd,
        '--access', 'full',
        "user create")

    out, ret = cluster.rgw_admin(cmd, check_retcode=False)
    if ret != 0:
        raise TestException("failed command: user create --uid %s" % uid)
    try:
        outj = json.loads(out.decode('utf-8'))
    except ValueError:
        raise TestException("invalid json after: user create --uid %s" % uid)
    if not isinstance(outj, dict):
        raise TestException("bad json after: user create --uid %s" % uid)
    if tid:
        tid_uid = "%s$%s" % (tid, uid)
    else:
        tid_uid = uid
    if outj['user_id'] != tid_uid:
        raise TestException(
            "command: user create --uid %s, returned user_id %s" %
            (tid_uid, outj['user_id']))

    #
    # For now, this uses hardcoded passwords based on uid.
    # They are all different for ease of debugging in case something crosses.
    #
    test5_add_s3_key(cluster, tid, uid)
    if subid:
        test5_add_swift_key(cluster, tid, uid, subid)

def test5_poke_s3(cluster):

    bucketname = "test5cont1"
    objname = "obj1"

    # Not sure if we like useless information printed, but the rest of the
    # test framework is insanely talkative when it executes commands.
    # So, to keep it in line and have a marker when things go wrong, this.
    print("PUT bucket %s object %s for tenant A (empty)" %
          (bucketname, objname))
    c = S3Connection(
        aws_access_key_id="tester5a",
        aws_secret_access_key="tester5apass",
        is_secure=False,
        host="localhost",
        port = cluster.port,
        calling_format = OrdinaryCallingFormat())

    bucket = c.create_bucket(bucketname)

    key = bucket.new_key(objname)
    headers = { "Content-Type": "text/plain" }
    key.set_contents_from_string(b"Test5A\n", headers)
    key.set_acl('public-read')

    #
    # Now it's getting interesting. We're logging into a tenantized user.
    #
    print("PUT bucket %s object %s for tenant B" % (bucketname, objname))
    c = S3Connection(
        aws_access_key_id="tester5b1",
        aws_secret_access_key="tester5b1pass",
        is_secure=False,
        host="localhost",
        port = cluster.port,
        calling_format = OrdinaryCallingFormat())

    bucket = c.create_bucket(bucketname)
    bucket.set_canned_acl('public-read')

    key = bucket.new_key(objname)
    headers = { "Content-Type": "text/plain" }
    key.set_contents_from_string(b"Test5B\n", headers)
    key.set_acl('public-read')

    #
    # Finally, let's fetch a couple of objects and verify that they
    # are what they should be and we didn't get them overwritten.
    # Note that we access one of objects across tenants using the colon.
    #
    print("GET bucket %s object %s for tenants A and B" %
          (bucketname, objname))
    c = S3Connection(
        aws_access_key_id="tester5a",
        aws_secret_access_key="tester5apass",
        is_secure=False,
        host="localhost",
        port = cluster.port,
        calling_format = OrdinaryCallingFormat())

    bucket = c.get_bucket(bucketname)

    key = bucket.get_key(objname)
    body = key.get_contents_as_string()
    if body != b"Test5A\n":
        raise TestException("failed body check, bucket %s object %s" %
                            (bucketname, objname))

    bucket = c.get_bucket("test5b:"+bucketname)
    key = bucket.get_key(objname)
    body = key.get_contents_as_string()
    if body != b"Test5B\n":
        raise TestException(
            "failed body check, tenant %s bucket %s object %s" %
            ("test5b", bucketname, objname))

    print("Poke OK")


def test5(cluster):
    # Plan:
    # 0. create users tester5a and test5b$tester5b1 test5b$tester5b2
    # 1. create buckets "test5cont" under test5a and test5b
    # 2. create objects in the buckets
    # 3. access objects (across users in container test5b)

    test5_make_user(cluster, None, "tester5a", "test5a")
    test5_make_user(cluster, "test5b", "tester5b1", "test5b1")
    test5_make_user(cluster, "test5b", "tester5b2", "test5b2")

    test5_poke_s3(cluster)


# XXX this parse_args boolean makes no sense. we should pass argv[] instead,
#     possibly empty. (copied from test_multi, correct it there too)
def init(parse_args):

    #argv = []
    #if parse_args:
    #    argv = sys.argv[1:]
    #args = parser.parse_args(argv)

    #rgw_multi = RGWMulti(int(args.num_zones))
    #rgw_multi.setup(not args.no_bootstrap)

    # __init__():
    port = 8001
    clnum = 1  # number of clusters
    clid = 1   # 1-based
    cluster = t.RGWCluster(clid, port)

    # setup():
    cluster.start()
    cluster.start_rgw()

    # The cluster is always reset at this point, so we don't need to list
    # users or delete pre-existing users.

    try:
        test2(cluster)
        test3(cluster)
        test4(cluster)
        test5(cluster)
    except TestException as e:
        cluster.stop_rgw()
        cluster.stop()
        sys.stderr.write("FAIL\n")
        sys.stderr.write("%s\n" % str(e))
        return 1

    # teardown():
    cluster.stop_rgw()
    cluster.stop()
    return 0

def setup_module():
    return init(False)

if __name__ == "__main__":
    sys.exit(init(True))
