# 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.
#
# Maintainer:
# Based on code written by Jed Smith <jed@jedsmith.org> who based it on
# code written by Alex Polvi <polvi@cloudkick.com>
#

import sys
import unittest
import json

from libcloud.utils.py3 import httplib

from libcloud.compute.drivers.equinixmetal import EquinixMetalNodeDriver
from libcloud.compute.base import Node, KeyPair
from libcloud.compute.types import NodeState

from libcloud.test import MockHttp
from libcloud.test.compute import TestCaseMixin
from libcloud.test.file_fixtures import ComputeFileFixtures

# This is causing test failures inder Python 3.5
import libcloud.compute.drivers.equinixmetal
libcloud.compute.drivers.equinixmetal.USE_ASYNC_IO_IF_AVAILABLE = False

__all__ = [
    'EquinixMetalTest'
]


class EquinixMetalTest(unittest.TestCase, TestCaseMixin):
    def setUp(self):
        EquinixMetalNodeDriver.connectionCls.conn_class = EquinixMetalMockHttp
        self.driver = EquinixMetalNodeDriver('foo')

    def test_list_nodes(self):
        nodes = self.driver.list_nodes('project-id')
        self.assertEqual(len(nodes), 1)
        node = nodes[0]
        self.assertEqual(node.id, '1e52437e-bbbb-cccc-dddd-74a9dfd3d3bb')
        self.assertEqual(node.name, 'test-node')
        self.assertEqual(node.state, NodeState.RUNNING)
        self.assertTrue('147.75.255.255' in node.public_ips)
        self.assertTrue('2604:EEEE::EE' in node.public_ips)
        self.assertTrue('10.0.0.255' in node.private_ips)
        self.assertEqual(node.extra['created_at'], '2015-05-03T15:50:49Z')
        self.assertEqual(node.extra['updated_at'], '2015-05-03T16:00:08Z')
        self.assertEqual(node.extra['billing_cycle'], 'hourly')
        self.assertEqual(node.extra['locked'], False)
        self.assertEqual(node.size.id, 'baremetal_1')
        self.assertEqual(node.size.name, 'Type 1 - 16384 RAM')
        self.assertEqual(node.size.ram, 16384)
        self.assertEqual(node.size.disk, 240)
        self.assertEqual(node.size.price, 0.4)
        self.assertEqual(node.size.extra['line'], 'baremetal')
        self.assertEqual(node.image.id, 'ubuntu_14_04')
        self.assertEqual(node.image.name, 'Ubuntu 14.04 LTS')
        self.assertEqual(node.image.extra['distro'], 'ubuntu')
        self.assertEqual(node.image.extra['version'], '14.04')

    def test_list_nodes_response(self):
        nodes = self.driver.list_nodes('project-id')
        self.assertTrue(isinstance(nodes, list))
        for node in nodes:
            self.assertTrue(isinstance(node, Node))

    def test_list_locations(self):
        locations = self.driver.list_locations()
        self.assertEqual(len(locations), 1)

    def test_list_images(self):
        images = self.driver.list_images()
        self.assertEqual(len(images), 4)

    def test_list_sizes(self):
        sizes = self.driver.list_sizes()
        self.assertEqual(len(sizes), 1)

    def test_create_node(self):
        node = self.driver.create_node(ex_project_id="project-id",
                                       name="node-name",
                                       size=self.driver.list_sizes()[0],
                                       image=self.driver.list_images()[0],
                                       location=self.driver.list_locations()[
                                           0])
        self.assertTrue(isinstance(node, Node))

    def test_create_node_response(self):
        size = self.driver.list_sizes()[0]
        image = self.driver.list_images()[0]
        location = self.driver.list_locations()[0]
        node = self.driver.create_node(ex_project_id="project-id",
                                       name='node-name',
                                       image=image,
                                       size=size,
                                       location=location)
        self.assertTrue(isinstance(node, Node))

    def test_reboot_node(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.reboot_node(node)

    def test_reboot_node_response(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.reboot_node(node)

    def test_destroy_node(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.destroy_node(node)

    def test_destroy_node_response(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.destroy_node(node)

    def test_reinstall_node(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.ex_reinstall_node(node)

    def test_rescue_node(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.ex_rescue_node(node)

    def test_list_key_pairs(self):
        keys = self.driver.list_key_pairs()
        self.assertEqual(len(keys), 3)

    def test_create_key_pair(self):
        key = self.driver.create_key_pair(name="sshkey-name",
                                          public_key="ssh-rsa AAAAB3NzaC1yc2EA\
AAADAQABAAABAQDI4pIqzpb5g3992h+yr527VRcaB68KE4vPjWPPoiQws49KIs2NMcOzS9QE4641uW\
1u5ML2HgQdfYKMF/YFGnI1Y6xV637DjhDyZYV9LasUH49npSSJjsBcsk9JGfUpNAOdcgpFzK8V90ei\
OrOC5YncxdwwG8pwjFI9nNVPCl4hYEu1iXdyysHvkFfS2fklsNjLWrzfafPlaen+qcBxygCA0sFdW/\
7er50aJeghdBHnE2WhIKLUkJxnKadznfAge7oEe+3LLAPfP+3yHyvp2+H0IzmVfYvAjnzliYetqQ8p\
g5ZW2BiJzvqz5PebGS70y/ySCNW1qQmJURK/Wc1bt9en root@libcloud")
        self.assertTrue(isinstance(key, KeyPair))

    def test_delete_key_pair(self):
        key = self.driver.list_key_pairs()[0]
        self.driver.delete_key_pair(key)

    def test_ex_list_projects(self):
        projects = self.driver.ex_list_projects()
        self.assertEqual(len(projects), 3)

    def test_ex_get_bgp_config_for_project(self):
        config = self.driver.ex_get_bgp_config_for_project(ex_project_id='4b653fce-6405-4300-9f7d-c587b7888fe5')
        self.assertEqual(config.get('status'), 'enabled')

    def test_ex_get_bgp_config(self):
        config = self.driver.ex_get_bgp_config()
        self.assertEqual(len(config), 2)

    def test_ex_list_nodes_for_project(self):
        nodes = self.driver.ex_list_nodes_for_project(ex_project_id='4b653fce-6405-4300-9f7d-c587b7888fe5')
        self.assertEqual(nodes[0].public_ips, ['147.75.102.193', '2604:1380:2000:c100::3'])

    def test_ex_create_bgp_session(self):
        node = self.driver.list_nodes('project-id')[0]
        session = self.driver.ex_create_bgp_session(node, 'ipv4')
        self.assertEqual(session['status'], 'unknown')

    def test_ex_get_bgp_session(self):
        session = self.driver.ex_get_bgp_session(self.driver.ex_list_bgp_sessions()[0]['id'])
        self.assertEqual(session['status'], 'down')

    def test_ex_list_bgp_sessions_for_project(self):
        sessions = self.driver.ex_list_bgp_sessions_for_project(ex_project_id='4b653fce-6405-4300-9f7d-c587b7888fe5')
        self.assertEqual(sessions['bgp_sessions'][0]['status'], 'down')

    def test_ex_list_bgp_sessions_for_node(self):
        sessions = self.driver.ex_list_bgp_sessions_for_node(self.driver.list_nodes()[0])
        self.assertEqual(sessions['bgp_sessions'][0]['status'], 'down')

    def test_ex_list_bgp_sessions(self):
        sessions = self.driver.ex_list_bgp_sessions()
        self.assertEqual(sessions[0]['status'], 'down')

    def test_ex_delete_bgp_session(self):
        self.driver.ex_delete_bgp_session(session_uuid='08f6b756-758b-4f1f-bfaf-b9b5479822d7')

    def test_ex_list_events_for_node(self):
        events = self.driver.ex_list_events_for_node(self.driver.list_nodes()[0])
        self.assertEqual(events['events'][0]['ip'], '157.52.105.28')

    def test_ex_list_events_for_project(self):
        events = self.driver.ex_list_events_for_project(self.driver.ex_list_projects()[0])
        self.assertEqual(events['meta']['total'], len(events['events']))

    def test_ex_get_node_bandwidth(self):
        node = self.driver.list_nodes('project-id')[0]
        bw = self.driver.ex_get_node_bandwidth(node, 1553194476, 1553198076)
        self.assertTrue(len(bw['bandwidth'][0]['datapoints'][0]) > 0)

    def test_ex_update_node(self):
        node = self.driver.list_nodes('project-id')[0]
        self.driver.ex_update_node(node, description='new_description')

    def test_ex_describe_all_addresses_for_project(self):
        addresses = self.driver.ex_describe_all_addresses_for_project(
            '4b653fce-6405-4300-9f7d-c587b7888fe5')
        self.assertEqual(len(addresses), 5)

    def test_ex_describe_address(self):
        address = self.driver.ex_describe_address(
            ex_address_id='01c184f5-1413-4b0b-9f6d-ac993f6c9241')
        self.assertEqual(address['network'], '147.75.33.32')

    def test_ex_request_address_reservation(self):
        response = self.driver.ex_request_address_reservation(
            ex_project_id='3d27fd13-0466-4878-be22-9a4b5595a3df')
        assert response['global_ip']

    def test_ex_associate_address_with_node(self):
        node = self.driver.list_nodes('project-id')[0]
        response = self.driver.ex_associate_address_with_node(node, '147.75.40.2/32')
        assert response['enabled']

    def test_ex_disassociate_address_with_node(self):
        node = self.driver.list_nodes('project-id')[0]
        assignments = self.driver.ex_list_ip_assignments_for_node(node)
        for ip_assignment in assignments['ip_addresses']:
            if ip_assignment['gateway'] == '147.75.40.2':
                self.driver.ex_disassociate_address(
                    ip_assignment['id'])
                break

    def test_list_volumes(self):
        volumes = self.driver.list_volumes()
        assert len(volumes) == 2
        assert len(volumes[0].extra['attachments']) == 0

    def test_create_volume(self):
        location = self.driver.list_locations()[0]
        volume = self.driver.create_volume(
            10, location, description="test volume", plan="storage_1",
            ex_project_id='3d27fd13-0466-4878-be22-9a4b5595a3df')
        assert len(volume.extra['attachments']) == 0
        assert not volume.extra['locked']

    def test_attach_volume(self):
        attached = False
        volumes = self.driver.ex_list_volumes_for_project(ex_project_id='3d27fd13-0466-4878-be22-9a4b5595a3df')
        node = self.driver.ex_list_nodes_for_project(ex_project_id='3d27fd13-0466-4878-be22-9a4b5595a3df')[0]
        for vol in volumes:
            if len(vol.extra['attachments']) == 0:
                attached = self.driver.attach_volume(node, vol)
                break
        assert attached

    def test_detach_volume(self):
        detached = False
        volumes = self.driver.ex_list_volumes_for_project(ex_project_id='3d27fd13-0466-4878-be22-9a4b5595a3df')
        for vol in volumes:
            if len(vol.extra['attachments']) > 0:
                detached = self.driver.detach_volume(vol)
                break
        assert detached

    def test_destroy_volume(self):
        destroyed = False
        volumes = self.driver.ex_list_volumes_for_project(ex_project_id='3d27fd13-0466-4878-be22-9a4b5595a3df')
        for vol in volumes:
            if len(vol.extra['attachments']) == 0:
                destroyed = self.driver.destroy_volume(vol)
                break
        assert destroyed


class EquinixMetalMockHttp(MockHttp):
    fixtures = ComputeFileFixtures('equinixmetal')

    def _metal_v1_facilities(self, method, url, body, headers):
        body = self.fixtures.load('facilities.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_plans(self, method, url, body, headers):
        body = self.fixtures.load('plans.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_plans(self, method, url, body, headers):
        body = self.fixtures.load('plans.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects(self, method, url, body, headers):
        body = self.fixtures.load('projects.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4b653fce_6405_4300_9f7d_c587b7888fe5_devices(self, method, url, body, headers):
        body = self.fixtures.load('devices_for_project.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4a4bce6b_d2ef_41f8_95cf_0e2f32996440_devices(self, method, url, body, headers):
        body = self.fixtures.load('devices_for_project.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_devices(self, method, url, body, headers):
        body = self.fixtures.load('devices_for_project.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4b653fce_6405_4300_9f7d_c587b7888fe5_ips(self, method, url, body, headers):
        body = self.fixtures.load('project_ips.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_ips(self, method, url, body, headers):
        if method == 'POST':
            body = self.fixtures.load('reserve_ip.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4b653fce_6405_4300_9f7d_c587b7888fe5_bgp_config(self, method, url, body, headers):
        body = self.fixtures.load('bgp_config_project_1.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_bgp_config(self, method, url, body, headers):
        body = self.fixtures.load('bgp_config_project_1.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4a4bce6b_d2ef_41f8_95cf_0e2f32996440_bgp_config(self, method, url, body, headers):
        body = self.fixtures.load('bgp_config_project_3.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_operating_systems(self, method, url, body, headers):
        body = self.fixtures.load('operatingsystems.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_ssh_keys(self, method, url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('sshkeys.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
        if method == 'POST':
            body = self.fixtures.load('sshkey_create.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_ssh_keys_2c1a7f23_1dc6_4a37_948e_d9857d9f607c(self, method, url,
                                                                body, headers):
        if method == 'DELETE':
            return (httplib.OK, '', {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_project_id_devices(self, method, url, body, headers):
        if method == 'POST':
            body = self.fixtures.load('device_create.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
        elif method == 'GET':
            body = self.fixtures.load('devices.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_1e52437e_bbbb_cccc_dddd_74a9dfd3d3bb(self, method, url,
                                                               body, headers):
        if method in ['DELETE', 'PUT']:
            return (httplib.OK, '', {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_1e52437e_bbbb_cccc_dddd_74a9dfd3d3bb_actions(
            self, method, url, body, headers):
        return (httplib.OK, '', {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_1e52437e_bbbb_cccc_dddd_74a9dfd3d3bb_bgp_sessions(self,
                                                                            method, url, body, headers):
        if method == 'POST':
            body = self.fixtures.load('bgp_session_create.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_bgp_sessions_08f6b756_758b_4f1f_bfaf_b9b5479822d7(self, method, url,
                                                                    body, headers):
        body = self.fixtures.load('bgp_session_get.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4b653fce_6405_4300_9f7d_c587b7888fe5_bgp_sessions(self,
                                                                             method, url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('bgp_sessions.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_905037a4_967c_4e81_b364_3a0603aa071b_bgp_sessions(self,
                                                                            method, url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('bgp_sessions.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4a4bce6b_d2ef_41f8_95cf_0e2f32996440_bgp_sessions(self,
                                                                             method, url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('bgp_sessions.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_bgp_sessions(self,
                                                                             method, url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('bgp_sessions.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_events(self, method,
                                                                       url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('project_events.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_905037a4_967c_4e81_b364_3a0603aa071b_events(self, method,
                                                                      url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('device_events.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_1e52437e_bbbb_cccc_dddd_74a9dfd3d3bb_bandwidth(self, method,
                                                                         url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('node_bandwidth.json')
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_ips_01c184f5_1413_4b0b_9f6d_ac993f6c9241(self, method, url, body,
                                                           headers):
        body = self.fixtures.load('ip_address.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_devices_1e52437e_bbbb_cccc_dddd_74a9dfd3d3bb_ips(self, method, url,
                                                                   body, headers):
        if method == 'GET':
            body = self.fixtures.load('ip_assignments.json')
        elif method == 'POST':
            body = self.fixtures.load('associate_ip.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_ips_aea4ee0c_675f_4b77_8337_8e13b868dd9c(self, method, url, body,
                                                           headers):
        if method == 'DELETE':
            return (httplib.OK, '', {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_3d27fd13_0466_4878_be22_9a4b5595a3df_storage(self, method,
                                                                        url, body, headers):
        if method == 'GET':
            body = self.fixtures.load('volumes.json')
        elif method == 'POST':
            body = self.fixtures.load('create_volume.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4a4bce6b_d2ef_41f8_95cf_0e2f32996440_storage(self, method,
                                                                        url, body, headers):
        if method == 'GET':
            body = json.dumps({"volumes": []})
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_projects_4b653fce_6405_4300_9f7d_c587b7888fe5_storage(self, method,
                                                                        url, body, headers):
        if method == 'GET':
            body = json.dumps({"volumes": []})
            return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_storage_74f11291_fde8_4abf_8150_e51cda7308c3(self, method, url, body,
                                                               headers):
        if method == 'DELETE':
            return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.NO_CONTENT])

    def _metal_v1_storage_a08aaf76_e0ce_43aa_b9cd_cce0d4ae4f4c_attachments(self, method,
                                                                           url, body, headers):
        if method == 'POST':
            body = self.fixtures.load('attach_volume.json')
        return (httplib.OK, body, {}, httplib.responses[httplib.OK])

    def _metal_v1_storage_a08aaf76_e0ce_43aa_b9cd_cce0d4ae4f4c(self, method, url, body,
                                                               headers):
        if method == 'DELETE':
            return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.NO_CONTENT])

    def _metal_v1_storage_attachments_2c16a96f_bb4f_471b_8e2e_b5820b9e1603(self,
                                                                           method, url, body, headers):
        if method == 'DELETE':
            return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.NO_CONTENT])


if __name__ == '__main__':
    sys.exit(unittest.main())
