diff --git a/IM/InfrastructureList.py b/IM/InfrastructureList.py index 0d7d3173c..9ced7042a 100644 --- a/IM/InfrastructureList.py +++ b/IM/InfrastructureList.py @@ -46,7 +46,10 @@ def add_infrastructure(inf): """Add a new Infrastructure.""" with InfrastructureList._lock: - InfrastructureList.infrastructure_list[inf.id] = inf + if inf.id in InfrastructureList.infrastructure_list: + raise Exception("Trying to add an existing infrastructure ID.") + else: + InfrastructureList.infrastructure_list[inf.id] = inf @staticmethod def remove_inf(del_inf): @@ -174,9 +177,9 @@ def _get_data_from_db(db_url, inf_id=None, auth=None): if db.connect(): inf_list = {} if inf_id: - res = db.select("select * from inf_list where id = '%s'" % inf_id) + res = db.select("select data from inf_list where id = '%s'" % inf_id) else: - res = db.select("select * from inf_list where deleted = 0 order by id desc") + res = db.select("select data from inf_list where deleted = 0 order by id desc") if len(res) > 0: for elem in res: # inf_id = elem[0] @@ -184,9 +187,9 @@ def _get_data_from_db(db_url, inf_id=None, auth=None): # deleted = elem[2] try: if auth: - inf = IM.InfrastructureInfo.InfrastructureInfo.deserialize_auth(elem[3]) + inf = IM.InfrastructureInfo.InfrastructureInfo.deserialize_auth(elem[0]) else: - inf = IM.InfrastructureInfo.InfrastructureInfo.deserialize(elem[3]) + inf = IM.InfrastructureInfo.InfrastructureInfo.deserialize(elem[0]) inf_list[inf.id] = inf except: InfrastructureList.logger.exception( diff --git a/IM/REST.py b/IM/REST.py index 38c5a2059..3e1faab12 100644 --- a/IM/REST.py +++ b/IM/REST.py @@ -387,6 +387,23 @@ def RESTGetInfrastructureProperty(infid=None, prop=None): bottle.abort( 403, "'outputs' infrastructure property is not valid in this infrastructure") return format_output(res, default_type="application/json", field_name="outputs") + elif prop == "data": + accept = get_media_type('Accept') + if accept and "application/json" not in accept and "*/*" not in accept and "application/*" not in accept: + return return_error(415, "Unsupported Accept Media Types: %s" % accept) + + delete = False + if "delete" in bottle.request.params.keys(): + str_delete = bottle.request.params.get("delete").lower() + if str_delete in ['yes', 'true', '1']: + delete = True + elif str_delete in ['no', 'false', '0']: + delete = False + else: + return return_error(400, "Incorrect value in delete parameter") + + data = InfrastructureManager.ExportInfrastructure(infid, delete, auth) + return format_output(data, default_type="application/json", field_name="data") else: return return_error(404, "Incorrect infrastructure property") @@ -476,6 +493,35 @@ def RESTCreateInfrastructure(): return return_error(400, "Error Creating Inf.: " + str(ex)) +@app.route('/infrastructures', method='PUT') +def RESTImportInfrastructure(): + try: + auth = get_auth_header() + except: + return return_error(401, "No authentication data provided") + + try: + content_type = get_media_type('Content-Type') + data = bottle.request.body.read().decode("utf-8") + + if content_type: + if "application/json" not in content_type: + return return_error(415, "Unsupported Media Type %s" % content_type) + + new_id = InfrastructureManager.ImportInfrastructure(data, auth) + + bottle.response.headers['InfID'] = new_id + bottle.response.content_type = "text/uri-list" + res = get_full_url('/infrastructures/%s' % new_id) + + return format_output(res, "text/uri-list", "uri") + except InvaliddUserException as ex: + return return_error(401, "Error Impporting Inf.: " + str(ex)) + except Exception as ex: + logger.exception("Error Impporting Inf.") + return return_error(400, "Error Impporting Inf.: " + str(ex)) + + @app.route('/infrastructures/:infid/vms/:vmid', method='GET') def RESTGetVMInfo(infid=None, vmid=None): try: diff --git a/IM/VirtualMachine.py b/IM/VirtualMachine.py index 470a4da3b..dd6efa674 100644 --- a/IM/VirtualMachine.py +++ b/IM/VirtualMachine.py @@ -577,10 +577,36 @@ def setIps(self, public_ips, private_ips, remove_old=False): self.info.networks.append(public_net) num_net = self.getNumNetworkIfaces() - for public_ip in public_ips: - if public_ip not in private_ips: - vm_system.setValue('net_interface.%s.ip' % num_net, str(public_ip)) - vm_system.setValue('net_interface.%s.connection' % num_net, public_net.id) + real_public_ips = [public_ip for public_ip in public_ips if public_ip not in private_ips] + if real_public_ips: + vm_system.setValue('net_interface.%s.connection' % num_net, public_net.id) + if len(real_public_ips) > 1: + self.logger.warn("Node with more that one public IP!") + self.logger.debug(real_public_ips) + if len(real_public_ips) == 2: + ip1 = IPAddress(real_public_ips[0]) + ip2 = IPAddress(real_public_ips[1]) + if ip1.version != ip2.version: + self.logger.info("It seems that there are one IPv4 and other IPv6. Get the IPv4 one.") + if ip1.version == 4: + vm_system.setValue('net_interface.%s.ip' % num_net, str(real_public_ips[0])) + vm_system.setValue('net_interface.%s.ipv6' % num_net, str(real_public_ips[1])) + else: + vm_system.setValue('net_interface.%s.ip' % num_net, str(real_public_ips[1])) + vm_system.setValue('net_interface.%s.ipv6' % num_net, str(real_public_ips[0])) + else: + self.logger.info("It seems that both are from the same version first one will be used") + vm_system.setValue('net_interface.%s.ip' % num_net, str(real_public_ips[0])) + else: + self.logger.info("It seems that there are more that 2 last ones will be used") + for ip in real_public_ips: + if IPAddress(ip).version == 4: + vm_system.setValue('net_interface.%s.ip' % num_net, str(ip)) + else: + vm_system.setValue('net_interface.%s.ipv6' % num_net, str(ip)) + else: + # The usual case + vm_system.setValue('net_interface.%s.ip' % num_net, str(real_public_ips[0])) if private_ips: private_net_map = {} diff --git a/doc/source/REST.rst b/doc/source/REST.rst index e38c2e014..8c1fabe32 100644 --- a/doc/source/REST.rst +++ b/doc/source/REST.rst @@ -21,8 +21,8 @@ Next tables summaries the resources and the HTTP methods available. | **POST** | | **Create** a new infrastructure | | **Create** a new virtual machine | | **Alter** VM properties based on | | | | based on the RADL posted | | based on the RADL posted. | | then RADL posted | +-------------+------------------------------------+------------------------------------+-------------------------------------------+ -| **PUT** | | | | **Modify** the virtual machine based on | -| | | | | the RADL posted. | +| **PUT** | | | **Import** an infrastructure | | **Modify** the virtual machine based on | +| | | | from another IM instance | | the RADL posted. | +-------------+------------------------------------+------------------------------------+-------------------------------------------+ | **DELETE** | | | **Undeploy** all the virtual | | **Undeploy** the virtual machine. | | | | | machines in the infrastructure. | | @@ -39,8 +39,8 @@ Next tables summaries the resources and the HTTP methods available. +=============+=====================================================+====================================================+ | **GET** | | **Get** the specified property ``property_name`` | | **Get** the specified property ``property_name`` | | | | associated to the machine ``vmId`` in ``infId``. | | associated to the infrastructure ``infId``. | -| | | It has one special property: ``contmsg``. | | It has three properties: ``contmsg``, ``radl``, | -| | | | ``state`` and ``outputs``. | +| | | It has one special property: ``contmsg``. | | It has five properties: ``contmsg``, ``radl``, | +| | | | ``state``, ``outputs`` and ``data``. | +-------------+-----------------------------------------------------+----------------------------------------------------+ +-------------+-----------------------------------------------+------------------------------------------------+ @@ -104,6 +104,23 @@ POST ``http://imserver.com/infrastructures`` "uri" : "http://server.com:8800/infrastructures/inf_id } +PUT ``http://imserver.com/infrastructures`` + :body: ``JSON data of the infrastructure`` + :body Content-type: application/json + :Response Content-type: text/uri-list + :ok response: 200 OK + :fail response: 401, 400, 415 + + Take control of the infrastructure serialized in in the body and return + the ID associated in the server. (See GET /infrastructures//data). + + If success, it is returned the URI of the new infrastructure. + The result is JSON format has the following format:: + + { + "uri" : "http://server.com:8800/infrastructures/inf_id + } + GET ``http://imserver.com/infrastructures/`` :Response Content-type: text/uri-list or application/json :ok response: 200 OK @@ -122,7 +139,7 @@ GET ``http://imserver.com/infrastructures/`` GET ``http://imserver.com/infrastructures//`` :Response Content-type: text/plain or application/json :ok response: 200 OK - :input fields: ``headeronly`` (optional) + :input fields: ``headeronly`` (optional), ``delete`` (optional) :fail response: 401, 404, 400, 403 Return property ``property_name`` associated to the infrastructure with ID ``infId``. It has three properties: @@ -131,6 +148,9 @@ GET ``http://imserver.com/infrastructures//`` 'true' or '1' only the initial part of the infrastructure contextualization log will be returned (without any VM contextualization log). :``radl``: a string with the original specified RADL of the infrastructure. + :``data``: a string with the JSOMN serialized data of the infrastructure. In case of ``delete`` flag is set to 'yes', + 'true' or '1' the data not only will be exported but also the infrastructure will be set deleted + (the virtual infrastructure will not be modified). :``state``: a JSON object with two elements: :``state``: a string with the aggregated state of the infrastructure. @@ -139,7 +159,7 @@ GET ``http://imserver.com/infrastructures//`` The result is JSON format has the following format:: { - ["radl"|"state"|"contmsg"|"outputs"]: + ["radl"|"state"|"contmsg"|"outputs"|"data"]: } POST ``http://imserver.com/infrastructures/`` diff --git a/test/integration/TestIM.py b/test/integration/TestIM.py index 3c4cc015a..4ab98780f 100755 --- a/test/integration/TestIM.py +++ b/test/integration/TestIM.py @@ -474,7 +474,7 @@ def test_40_export_import(self): Test ExportInfrastructure and ImportInfrastructure functions """ (success, res) = self.server.ExportInfrastructure( - self.inf_id, False, self.auth_data) + self.inf_id, True, self.auth_data) self.assertTrue( success, msg="ERROR calling ExportInfrastructure: " + str(res)) diff --git a/test/unit/REST.py b/test/unit/REST.py index 039ecc77d..585b09df6 100755 --- a/test/unit/REST.py +++ b/test/unit/REST.py @@ -51,6 +51,7 @@ RESTStopVM, RESTGeVersion, RESTCreateDiskSnapshot, + RESTImportInfrastructure, return_error, format_output) @@ -702,6 +703,35 @@ def test_CreateDiskSnapshot(self, bottle_request, CreateDiskSnapshot): res = RESTCreateDiskSnapshot("1", "1", 0) self.assertEqual(res, "Error creating snapshot: Invalid VM ID") + @patch("IM.InfrastructureManager.InfrastructureManager.ExportInfrastructure") + @patch("bottle.request") + def test_ExportInfrastructure(self, bottle_request, ExportInfrastructure): + """Test REST StopInfrastructure.""" + bottle_request.return_value = MagicMock() + bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + + ExportInfrastructure.return_value = "strinf" + + res = RESTGetInfrastructureProperty("1", "data") + self.assertEqual(res, '{"data": "strinf"}') + + @patch("IM.InfrastructureManager.InfrastructureManager.ImportInfrastructure") + @patch("bottle.request") + def test_ImportInfrastructure(self, bottle_request, ImportInfrastructure): + """Test REST StopInfrastructure.""" + bottle_request.return_value = MagicMock() + bottle_request.headers = {"AUTHORIZATION": ("type = InfrastructureManager; username = user; password = pass\n" + "id = one; type = OpenNebula; host = onedock.i3m.upv.es:2633; " + "username = user; password = pass")} + bottle_request.environ = {'HTTP_HOST': 'imserver.com'} + + ImportInfrastructure.return_value = "newid" + + res = RESTImportInfrastructure() + self.assertEqual(res, "http://imserver.com/infrastructures/newid") + @patch("IM.REST.get_media_type") def test_return_error(self, get_media_type): get_media_type.return_value = ["application/json"] diff --git a/test/unit/connectors/OpenStack.py b/test/unit/connectors/OpenStack.py index a562f30bd..277b529b4 100755 --- a/test/unit/connectors/OpenStack.py +++ b/test/unit/connectors/OpenStack.py @@ -234,8 +234,8 @@ def test_30_updateVMInfo(self, get_driver): node = MagicMock() node.id = "1" node.state = "running" - node.extra = {'flavorId': 'small', 'addresses': {'os-lan': [{'addr': '10.0.0.1', - 'OS-EXT-IPS:type': 'fixed'}]}} + node.extra = {'flavorId': 'small', + 'addresses': {'os-lan': [{'addr': '10.0.0.1', 'OS-EXT-IPS:type': 'fixed'}]}} node.public_ips = [] node.private_ips = ['10.0.0.1'] node.driver = driver @@ -268,7 +268,7 @@ def test_30_updateVMInfo(self, get_driver): self.assertEquals(vm.info.systems[0].getValue("net_interface.1.ip"), "10.0.0.1") # In this case the Node has the float ip assigned - node.public_ips = ['8.8.8.8'] + # node.public_ips = ['8.8.8.8'] floating_ip.node_id = node.id pool.list_floating_ips.return_value = [floating_ip] driver.ex_list_floating_ip_pools.return_value = [pool] @@ -286,6 +286,21 @@ def test_30_updateVMInfo(self, get_driver): self.assertEquals(vm.info.systems[0].getValue("net_interface.0.ip"), "8.8.8.8") self.assertTrue(success, msg="ERROR: updating VM info.") + + # the node has a IPv6 IP + node = MagicMock() + node.id = "2" + node.state = "running" + node.extra = {'flavorId': 'small'} + node.public_ips = ['8.8.8.8', '2001:630:12:581:f816:3eff:fe92:2146'] + node.private_ips = ['10.0.0.1'] + node.driver = driver + driver.ex_get_node_details.return_value = node + + success, vm = ost_cloud.updateVMInfo(vm, auth) + self.assertTrue(success, msg="ERROR: updating VM info.") + self.assertEquals(vm.info.systems[0].getValue("net_interface.0.ip"), "8.8.8.8") + self.assertEquals(vm.info.systems[0].getValue("net_interface.0.ipv6"), "2001:630:12:581:f816:3eff:fe92:2146") self.assertNotIn("ERROR", self.log.getvalue(), msg="ERROR found in log: %s" % self.log.getvalue()) @patch('libcloud.compute.drivers.openstack.OpenStackNodeDriver')