# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

"""
Tests of acquiring a specified public IP for isolated network or vpc
"""

# Import Local Modules
from nose.plugins.attrib import attr
from marvin.cloudstackTestCase import cloudstackTestCase, unittest
from marvin.cloudstackAPI import createVlanIpRange
from marvin.lib.utils import (validateList,
                              cleanup_resources)
from marvin.lib.base import (Account,
                             Domain,
                             Configurations,
                             VirtualMachine,
                             ServiceOffering,
                             VpcOffering,
                             Zone,
                             Network,
                             VPC,
                             PublicIPAddress,
                             PublicIpRange)
from marvin.lib.common import (get_domain,
                               get_zone,
                               get_free_vlan,
                               get_template)
import logging
import random

class TestAcquireSpecifiedPublicIp(cloudstackTestCase):
    @classmethod
    def setUpClass(cls):
        cls.testClient = super(
            TestAcquireSpecifiedPublicIp,
            cls).getClsTestClient()
        cls.apiclient = cls.testClient.getApiClient()
        cls.services = cls.testClient.getParsedTestDataConfig()

        zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
        cls.zone = Zone(zone.__dict__)
        cls.template = get_template(cls.apiclient, cls.zone.id)
        cls._cleanup = []

        if str(cls.zone.securitygroupsenabled) == "True":
            sys.exit(1)

        cls.logger = logging.getLogger("TestAcquireSpecifiedPublicIp")
        cls.stream_handler = logging.StreamHandler()
        cls.logger.setLevel(logging.DEBUG)
        cls.logger.addHandler(cls.stream_handler)

        # Get Zone, Domain and templates
        cls.domain = get_domain(cls.apiclient)

        # Create new domain1
        cls.domain1 = Domain.create(
            cls.apiclient,
            services=cls.services["acl"]["domain1"],
            parentdomainid=cls.domain.id)

        # Create account1
        cls.account1 = Account.create(
            cls.apiclient,
            cls.services["acl"]["accountD1"],
            domainid=cls.domain1.id
        )

        # Create domain2
        cls.domain2 = Domain.create(
            cls.apiclient,
            services=cls.services["acl"]["domain2"],
            parentdomainid=cls.domain.id)

        # Create account2
        cls.account2 = Account.create(
            cls.apiclient,
            cls.services["acl"]["accountD2"],
            domainid=cls.domain2.id
        )

        cls.services["publiciprange"]["zoneid"] = cls.zone.id
        cls.services["publiciprange"]["forvirtualnetwork"] = "true"

        # Create public ip range 1
        cls.services["publiciprange"]["vlan"] = get_free_vlan(
            cls.apiclient,
            cls.zone.id)[1]
        random_subnet_number = random.randrange(10,20)
        cls.services["publiciprange"]["gateway"] = "172.16." + \
            str(random_subnet_number) + ".1"
        cls.services["publiciprange"]["startip"] = "172.16." + \
            str(random_subnet_number) + ".2"
        cls.services["publiciprange"]["endip"] = "172.16." + \
            str(random_subnet_number) + ".10"
        cls.services["publiciprange"]["netmask"] = "255.255.255.0"
        cls.public_ip_range1 = PublicIpRange.create(
            cls.apiclient,
            cls.services["publiciprange"]
        )
        PublicIpRange.dedicate(
            cls.apiclient,
            cls.public_ip_range1.vlan.id,
            domainid=cls.account1.domainid
        )

        # Create public ip range 2
        cls.services["publiciprange"]["vlan"] = get_free_vlan(
            cls.apiclient,
            cls.zone.id)[1]
        cls.services["publiciprange"]["gateway"] = "172.16." + \
            str(random_subnet_number + 1) + ".1"
        cls.services["publiciprange"]["startip"] = "172.16." + \
            str(random_subnet_number + 1) + ".2"
        cls.services["publiciprange"]["endip"] = "172.16." + \
            str(random_subnet_number + 1) + ".10"
        cls.services["publiciprange"]["netmask"] = "255.255.255.0"
        cls.public_ip_range2 = PublicIpRange.create(
            cls.apiclient,
            cls.services["publiciprange"]
        )
        PublicIpRange.dedicate(
            cls.apiclient,
            cls.public_ip_range2.vlan.id,
            account=cls.account1.name,
            domainid=cls.account1.domainid
        )

        # Create public ip range 3
        cls.services["publiciprange"]["vlan"] = get_free_vlan(
            cls.apiclient,
            cls.zone.id)[1]
        cls.services["publiciprange"]["gateway"] = "172.16." + \
            str(random_subnet_number + 2) + ".1"
        cls.services["publiciprange"]["startip"] = "172.16." + \
            str(random_subnet_number + 2) + ".2"
        cls.services["publiciprange"]["endip"] = "172.16." + \
            str(random_subnet_number + 2) + ".10"
        cls.services["publiciprange"]["netmask"] = "255.255.255.0"
        cls.public_ip_range3 = PublicIpRange.create(
            cls.apiclient,
            cls.services["publiciprange"]
        )
        PublicIpRange.dedicate(
            cls.apiclient,
            cls.public_ip_range3.vlan.id,
            domainid=cls.account2.domainid
        )

        # Create public ip range 4
        cls.services["publiciprange"]["vlan"] = get_free_vlan(
            cls.apiclient,
            cls.zone.id)[1]
        cls.services["publiciprange"]["gateway"] = "172.16." + \
            str(random_subnet_number + 3) + ".1"
        cls.services["publiciprange"]["startip"] = "172.16." + \
            str(random_subnet_number + 3) + ".2"
        cls.services["publiciprange"]["endip"] = "172.16." + \
            str(random_subnet_number + 3) + ".10"
        cls.services["publiciprange"]["netmask"] = "255.255.255.0"
        cls.public_ip_range4 = PublicIpRange.create(
            cls.apiclient,
            cls.services["publiciprange"]
        )
        PublicIpRange.dedicate(
            cls.apiclient,
            cls.public_ip_range4.vlan.id,
            account=cls.account2.name,
            domainid=cls.account2.domainid
        )

        # Create public ip range 5
        cls.services["publiciprange"]["vlan"] = get_free_vlan(
            cls.apiclient,
            cls.zone.id)[1]
        cls.services["publiciprange"]["gateway"] = "172.16." + \
            str(random_subnet_number + 4) + ".1"
        cls.services["publiciprange"]["startip"] = "172.16." + \
            str(random_subnet_number + 4) + ".2"
        cls.services["publiciprange"]["endip"] = "172.16." + \
            str(random_subnet_number + 4) + ".10"
        cls.services["publiciprange"]["netmask"] = "255.255.255.0"
        cls.public_ip_range5 = PublicIpRange.create(
            cls.apiclient,
            cls.services["publiciprange"]
        )

        cls._cleanup.append(cls.account1)
        cls._cleanup.append(cls.domain1)
        cls._cleanup.append(cls.account2)
        cls._cleanup.append(cls.domain2)
        cls._cleanup.append(cls.public_ip_range1)
        cls._cleanup.append(cls.public_ip_range2)
        cls._cleanup.append(cls.public_ip_range3)
        cls._cleanup.append(cls.public_ip_range4)
        cls._cleanup.append(cls.public_ip_range5)

    @classmethod
    def tearDownClass(cls):
        try:
            cleanup_resources(cls.apiclient, cls._cleanup)
        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)
        return

    def setUp(cls):
        cls.apiclient = cls.testClient.getApiClient()
        cls.cleanup = []
        return

    def tearDown(cls):
        try:
            cleanup_resources(cls.apiclient, cls.cleanup)
        except Exception as e:
            raise Exception("Warning: Exception during cleanup : %s" % e)
        return

    @attr(tags=["advanced"], required_hardware="false")
    def test_01_acquire_public_ip_in_isolated_network(self):
        # Validate the following
        # 1. create a vm . it will create a network as well.
        # 2. assign a specified IP from subnet which is dedicated to domain1, it should succeed
        # 3. assign a specified IP from subnet which is dedicated to domain1/account1, it should succeed
        # 4. assign a specified IP from subnet which is dedicated to domain2, it should fail
        # 5. assign a specified IP from subnet which is dedicated to domain2/account2, it should fail
        # 6. update account setting use.system.public.ips to false, assign a specified IP from subnet which is public, it should fail
        # 7. update account setting use.system.public.ips to true, assign a specified IP from subnet which is public, it should succeed

        self.service_offering = ServiceOffering.create(
            self.apiclient,
            self.services["service_offering"]
        )
        self.cleanup.append(self.service_offering)

        self.virtual_machine = VirtualMachine.create(
            self.apiclient,
            self.services["small"],
            templateid=self.template.id,
            accountid=self.account1.name,
            domainid=self.account1.domainid,
            serviceofferingid=self.service_offering.id,
            zoneid=self.zone.id
        )

        networks = Network.list(
            self.apiclient,
            account=self.account1.name,
            domainid=self.account1.domainid,
            listall=True
        )
        self.assertEqual(
            isinstance(networks, list),
            True,
            "List networks should return a valid response for created network"
             )
        network = networks[0]

        # Associate IP in range dedicated to domain1
        ip_address_1 = self.get_free_ipaddress(self.public_ip_range1.vlan.id)
        ipaddress = PublicIPAddress.create(
            self.apiclient,
            zoneid=self.zone.id,
            networkid=network.id,
            ipaddress=ip_address_1
        )
        self.assertIsNotNone(
            ipaddress,
            "Failed to Associate IP Address"
        )
        self.cleanup.append(ipaddress)
        self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_1, "Associated IP is not same as specified")

        # Associate IP in range dedicated to domain1/account1
        ip_address_2 = self.get_free_ipaddress(self.public_ip_range2.vlan.id)
        ipaddress = PublicIPAddress.create(
            self.apiclient,
            zoneid=self.zone.id,
            networkid=network.id,
            ipaddress=ip_address_2
        )
        self.assertIsNotNone(
            ipaddress,
            "Failed to Associate IP Address"
        )
        self.cleanup.append(ipaddress)
        self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_2, "Associated IP is not same as specified")

        # Associate IP in range dedicated to domain2
        ip_address_3 = self.get_free_ipaddress(self.public_ip_range3.vlan.id)
        with self.assertRaises(Exception):
            ipaddress = PublicIPAddress.create(
                self.apiclient,
                zoneid=self.zone.id,
                networkid=network.id,
                ipaddress=ip_address_3
            )

        # Associate IP in range dedicated to domain2/account2
        ip_address_4 = self.get_free_ipaddress(self.public_ip_range4.vlan.id)
        with self.assertRaises(Exception):
            ipaddress = PublicIPAddress.create(
                self.apiclient,
                zoneid=self.zone.id,
                networkid=network.id,
                ipaddress=ip_address_4
            )

        # Associate IP in public IP pool
        Configurations.update(
            self.apiclient,
            name="use.system.public.ips",
            value="false",
            accountid=self.account1.id
        )

        ip_address_5 = self.get_free_ipaddress(self.public_ip_range5.vlan.id)
        with self.assertRaises(Exception):
            ipaddress = PublicIPAddress.create(
                self.apiclient,
                zoneid=self.zone.id,
                networkid=network.id,
                ipaddress=ip_address_5
            )

        Configurations.update(
            self.apiclient,
            name="use.system.public.ips",
            value="true",
            accountid=self.account1.id
        )
        ipaddress = PublicIPAddress.create(
            self.apiclient,
            zoneid=self.zone.id,
            networkid=network.id,
            ipaddress=ip_address_5
        )
        self.assertIsNotNone(
            ipaddress,
            "Failed to Associate IP Address"
        )
        self.cleanup.append(ipaddress)
        self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_5, "Associated IP is not same as specified")

        self.cleanup.append(self.virtual_machine)
        self.cleanup.append(self.service_offering)

        return

    @attr(tags=["advanced"], required_hardware="false")
    def test_02_acquire_public_ip_in_vpc(self):
        # Validate the following
        # 1. create a VPC with default offering.
        # 2. assign a specified IP from subnet which is dedicated to domain1, it should succeed
        # 3. assign a specified IP from subnet which is dedicated to domain1/account1, it should succeed
        # 4. assign a specified IP from subnet which is dedicated to domain2, it should fail
        # 5. assign a specified IP from subnet which is dedicated to domain2/account2, it should fail
        # 6. update account setting use.system.public.ips to false, assign a specified IP from subnet which is public, it should fail
        # 7. update account setting use.system.public.ips to true, assign a specified IP from subnet which is public, it should succeed

        vpcOffering = VpcOffering.list(self.apiclient, name="Default VPC offering")
        vpc = VPC.create(
            apiclient=self.apiclient,
            services=self.services["vpc"],
            vpcofferingid=vpcOffering[0].id,
            zoneid=self.zone.id,
            account=self.account1.name,
            domainid=self.account1.domainid
        )

        # Associate IP in range dedicated to domain1
        ip_address_1 = self.get_free_ipaddress(self.public_ip_range1.vlan.id)
        ipaddress = PublicIPAddress.create(
            self.apiclient,
            zoneid=self.zone.id,
            vpcid=vpc.id,
            ipaddress=ip_address_1
        )
        self.assertIsNotNone(
            ipaddress,
            "Failed to Associate IP Address"
        )
        self.cleanup.append(ipaddress)
        self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_1, "Associated IP is not same as specified")

        # Associate IP in range dedicated to domain1/account1
        ip_address_2 = self.get_free_ipaddress(self.public_ip_range2.vlan.id)
        ipaddress = PublicIPAddress.create(
            self.apiclient,
            zoneid=self.zone.id,
            vpcid=vpc.id,
            ipaddress=ip_address_2
        )
        self.assertIsNotNone(
            ipaddress,
            "Failed to Associate IP Address"
        )
        self.cleanup.append(ipaddress)
        self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_2, "Associated IP is not same as specified")

        # Associate IP in range dedicated to domain2
        ip_address_3 = self.get_free_ipaddress(self.public_ip_range3.vlan.id)
        with self.assertRaises(Exception):
            ipaddress = PublicIPAddress.create(
                self.apiclient,
                zoneid=self.zone.id,
                vpcid=vpc.id,
                ipaddress=ip_address_3
            )

        # Associate IP in range dedicated to domain2/account2
        ip_address_4 = self.get_free_ipaddress(self.public_ip_range4.vlan.id)
        with self.assertRaises(Exception):
            ipaddress = PublicIPAddress.create(
                self.apiclient,
                zoneid=self.zone.id,
                vpcid=vpc.id,
                ipaddress=ip_address_4
            )

        # Associate IP in public IP pool
        Configurations.update(
            self.apiclient,
            name="use.system.public.ips",
            value="false",
            accountid=self.account1.id
        )

        ip_address_5 = self.get_free_ipaddress(self.public_ip_range5.vlan.id)
        with self.assertRaises(Exception):
            ipaddress = PublicIPAddress.create(
                self.apiclient,
                zoneid=self.zone.id,
                vpcid=vpc.id,
                ipaddress=ip_address_5
            )

        Configurations.update(
            self.apiclient,
            name="use.system.public.ips",
            value="true",
            accountid=self.account1.id
        )
        ipaddress = PublicIPAddress.create(
            self.apiclient,
            zoneid=self.zone.id,
            vpcid=vpc.id,
            ipaddress=ip_address_5
        )
        self.assertIsNotNone(
            ipaddress,
            "Failed to Associate IP Address"
        )
        self.cleanup.append(ipaddress)
        self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_5, "Associated IP is not same as specified")

        self.cleanup.append(vpc)
        return

    def get_free_ipaddress(self, vlanId):
        ipaddresses = PublicIPAddress.list(
            self.apiclient,
            vlanid=vlanId,
            state='Free'
        )
        self.assertEqual(
            isinstance(ipaddresses, list),
            True,
            "List ipaddresses should return a valid response for Free ipaddresses"
             )
        random.shuffle(ipaddresses)
        return ipaddresses[0].ipaddress
