diff --git a/README.md b/README.md index be7b6cc..2101f91 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ manager = packet.Manager(auth_token="yourapiauthtoken") device = manager.create_device(project_id='project-id', hostname='node-name-of-your-choice', - plan='baremetal_1', facility='ewr1', + plan='baremetal_1', metro='sv', operating_system='ubuntu_18_04') print(device) ``` diff --git a/packet/Device.py b/packet/Device.py index b3cfa2e..a6d2491 100644 --- a/packet/Device.py +++ b/packet/Device.py @@ -28,6 +28,7 @@ def __init__(self, data, manager): self.customdata = data.get("customdata", None) self.operating_system = OperatingSystem(data["operating_system"]) self.facility = data.get("facility") + self.metro = data.get("metro") self.project = data.get("project") self.ssh_keys = data.get("ssh_keys") self.project_lite = data.get("project_lite") diff --git a/packet/DeviceBatch.py b/packet/DeviceBatch.py index 86cbaa5..316f626 100644 --- a/packet/DeviceBatch.py +++ b/packet/DeviceBatch.py @@ -8,6 +8,7 @@ def __init__(self, data): self.plan = data.get("plan") self.operating_system = data.get("operating_system") self.facility = data.get("facility") + self.metro = data.get("metro") self.quantity = data.get("quantity") def __str__(self): diff --git a/packet/IPAddress.py b/packet/IPAddress.py index fcda407..cce5608 100644 --- a/packet/IPAddress.py +++ b/packet/IPAddress.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LGPL-3.0-only from .Facility import Facility +from .Metro import Metro class IPAddress: @@ -22,7 +23,6 @@ def __init__(self, data, manager): self.customdata = data.get("customdata") self.project = data.get("project") self.project_lite = data.get("project_lite") - self.facility = Facility(data.get("facility")) self.details = data.get("details") self.assigned_to = data.get("assigned_to") self.interface = data.get("interface") @@ -30,6 +30,11 @@ def __init__(self, data, manager): self.address = data.get("address") self.gateway = data.get("gateway") + facility = data.get("facility") + self.facility = Facility(facility) if facility else None + metro = data.get("metro") + self.metro = Metro(metro) if metro else None + def delete(self): return self.manager.call_api("ips/%s" % self.id, type="DELETE") diff --git a/packet/Manager.py b/packet/Manager.py index 01ad960..d8d1d59 100644 --- a/packet/Manager.py +++ b/packet/Manager.py @@ -10,6 +10,7 @@ from .SSHKey import SSHKey from .Project import Project from .Facility import Facility +from .Metro import Metro from .OperatingSystem import OperatingSystem from .Volume import Volume from .BGPConfig import BGPConfig @@ -41,6 +42,14 @@ def list_facilities(self, params={}): facilities.append(facility) return facilities + def list_metros(self, params={}): + data = self.call_api("metros", params=params) + metros = list() + for jsoned in data["metros"]: + metro = Metro(jsoned) + metros.append(metro) + return metros + def list_plans(self, params={}): data = self.call_api("plans", params=params) plans = list() @@ -121,8 +130,8 @@ def create_device( project_id, hostname, plan, - facility, - operating_system, + facility="", + operating_system="", always_pxe=False, billing_cycle="hourly", features={}, @@ -139,11 +148,11 @@ def create_device( hardware_reservation_id="", storage={}, customdata={}, + metro="", ): params = { "billing_cycle": billing_cycle, - "facility": facility, "features": features, "hostname": hostname, "locked": locked, @@ -157,6 +166,10 @@ def create_device( "userdata": userdata, } + if metro != "": + params["metro"] = metro + if facility != "": + params["facility"] = facility if hardware_reservation_id != "": params["hardware_reservation_id"] = hardware_reservation_id if storage: @@ -319,6 +332,23 @@ def validate_capacity(self, servers): else: raise e + # servers is a list of tuples of metro, plan, and quantity. + def validate_metro_capacity(self, servers): + params = {"servers": []} + for server in servers: + params["servers"].append( + {"facility": server[0], "plan": server[1], "quantity": server[2]} + ) + + try: + data = self.call_api("/capacity/metros", "POST", params) + return all(s["available"] for s in data["servers"]) + except PacketError as e: # pragma: no cover + if e.args[0] == "Error 503: Service Unavailable": + return False + else: + raise e + def get_spot_market_prices(self, params={}): data = self.call_api("/market/spot/prices", params=params) return data["spot_market_prices"] @@ -405,20 +435,24 @@ def reserve_ip_address( project_id, type, quantity, - facility, + facility="", details=None, comments=None, tags=list(), + metro="", ): request = { "type": type, "quantity": quantity, - "facility": facility, "details": details, "comments": comments, "tags": tags, } + if facility != "": + request["facility"] = facility + if metro != "": + request["metro"] = metro data = self.call_api( "/projects/%s/ips" % project_id, params=request, type="POST" ) @@ -575,15 +609,18 @@ def list_vlans(self, project_id, params=None): return vlans def create_vlan( - self, project_id, facility, vxlan=None, vlan=None, description=None + self, project_id, facility="", vxlan=None, vlan=None, description=None, metro="" ): params = { "project_id": project_id, - "facility": facility, "vxlan": vxlan, "vlan": vlan, "description": description, } + if facility != "": + params["facility"] = facility + if metro != "": + params["metro"] = metro data = self.call_api( "projects/%s/virtual-networks" % project_id, type="POST", params=params ) @@ -638,14 +675,12 @@ def create_packet_connections(self, params): "project_id": params["project_id"], "provider_id": params["provider_id"], "provider_payload": params["provider_payload"], - "facility": params["facility"], "port_speed": params["port_speed"], "vlan": params["vlan"], } - if "tags" in params: - body["tags"] = params["tags"] - if "description" in params: - body["description"] = params["description"] + for opt in ["tags", "description", "facility", "metro"]: + if opt in params: + body[opt] = params[opt] data = self.call_api("/packet-connect/connections", type="POST", params=body) return data diff --git a/packet/Metro.py b/packet/Metro.py new file mode 100644 index 0000000..f979028 --- /dev/null +++ b/packet/Metro.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Metro: + def __init__(self, data): + self.id = data.get("id") + self.code = data.get("code") + self.name = data.get("name") + self.country = data.get("country") + + def __str__(self): + return "%s" % self.code + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Vlan.py b/packet/Vlan.py index d544b1f..4a70a52 100644 --- a/packet/Vlan.py +++ b/packet/Vlan.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: LGPL-3.0-only from packet import Project from .Facility import Facility +from .Metro import Metro class Vlan: @@ -15,9 +16,13 @@ def __init__(self, data, manager): self.vxlan = data.get("vxlan") self.internet_gateway = data.get("internet_gateway") self.facility_code = data.get("facility_code") + self.metro_code = data.get("metro_code") self.created_at = data.get("created_at") + facility = data.get("facility", None) + self.facility = Facility(facility) if facility else None + metro = data.get("metro", None) + self.metro = Metro(metro) if metro else None - self.facility = Facility(data.get("facility")) try: project_data = self.manager.call_api( data["assigned_to"]["href"], type="GET" diff --git a/packet/__init__.py b/packet/__init__.py index 8e604f0..1ac36c2 100644 --- a/packet/__init__.py +++ b/packet/__init__.py @@ -13,6 +13,7 @@ from .Email import Email # noqa from .Event import Event # noqa from .Facility import Facility # noqa +from .Metro import Metro # noqa from .OperatingSystem import OperatingSystem # noqa from .Plan import Plan # noqa from .Project import Project # noqa diff --git a/test/fixtures/get_metros.json b/test/fixtures/get_metros.json new file mode 100644 index 0000000..aa871d2 --- /dev/null +++ b/test/fixtures/get_metros.json @@ -0,0 +1 @@ +{"metros":[{"id":"0446ac38-aae0-45f5-959c-54d26c4fc3c7","name":"Phoenix","code":"px","country":"US"},{"id":"833225d2-a35a-49cf-a67c-1f84d5d11654","name":"Marseille","code":"mr","country":"FR"},{"id":"96a57b6d-c62c-41b5-ab8e-f8d63a7f9887","name":"Washington DC","code":"dc","country":"US"},{"id":"a04e45e4-4356-458c-b25a-b72cc54bdad1","name":"Atlanta","code":"at","country":"US"},{"id":"932eecda-6808-44b9-a3be-3abef49796ef","name":"New York","code":"ny","country":"US"},{"id":"108b2cfb-246b-45e3-885a-bf3e82fce1a0","name":"Amsterdam","code":"am","country":"NL"},{"id":"5afb3744-f80d-4b49-bf21-50ede9252cfd","name":"Toronto","code":"tr","country":"CA"},{"id":"d2a8e94f-2b42-4440-9c8e-8ef01defcd27","name":"Seoul","code":"sl","country":"KR"},{"id":"d50fd052-34ec-4977-a173-ad6f9266995d","name":"Hong Kong","code":"hk","country":"HK"},{"id":"f6ada324-8226-4bfc-99a8-453d47caf2dc","name":"Singapore","code":"sg","country":"SG"},{"id":"5f72cbf6-96e4-44f2-ae60-213888fa2b9f","name":"Tokyo","code":"ty","country":"JP"},{"id":"b1ac82b2-616c-4405-9424-457ef6edf9ae","name":"Frankfurt","code":"fr","country":"DE"},{"id":"d2f09853-a1aa-4b29-aa9d-682462fd8d1d","name":"Sydney","code":"sy","country":"AU"},{"id":"5f1d3910-2059-4737-8e7d-d4907cfbf27f","name":"London","code":"ld","country":"GB"},{"id":"91fa81e8-db1a-4da1-b218-7beeaab6f8bc","name":"Kansas City","code":"kc","country":"US"},{"id":"f2f0f0b3-5359-4296-a402-56abad842f56","name":"Paris","code":"pa","country":"FR"},{"id":"56c55e99-8125-4f65-af21-a8682e033804","name":"Houston","code":"ho","country":"US"},{"id":"b8ffa6c9-3da2-4cf1-b48d-049002b208fe","name":"Seattle","code":"se","country":"US"},{"id":"2991b022-b8c4-497e-8db7-5a407c3a209b","name":"Silicon Valley","code":"sv","country":"US"},{"id":"bb059cc0-0b2a-4f5b-8a55-219e6b4240da","name":"Los Angeles","code":"la","country":"US"},{"id":"60666d92-e00f-43a8-a9f8-fddf665390ca","name":"Chicago","code":"ch","country":"US"},{"id":"d3d6b29f-042d-43b7-b3ce-0bf53d5754ca","name":"Dallas","code":"da","country":"US"},{"id":"543f8059-65f6-4c2c-b7ad-6dac5eb87085","name":"Pittsburgh","code":"pi","country":"US"},{"id":"583a553a-465a-49f3-a8a9-5fa6b44aa519","name":"Detroit","code":"dt","country":"US"}]} diff --git a/test/fixtures/get_projects_438659f0_ips.json b/test/fixtures/get_projects_438659f0_ips.json new file mode 100644 index 0000000..fee6e43 --- /dev/null +++ b/test/fixtures/get_projects_438659f0_ips.json @@ -0,0 +1,98 @@ +{"ip_addresses": [{ + "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be1", + "address_family": 4, + "netmask": "255.255.255.254", + "created_at": "2019-07-18T08:46:38Z", + "details": null, + "tags": [], + "public": true, + "cidr": 31, + "management": true, + "manageable": true, + "enabled": true, + "global_ip": null, + "customdata": {}, + "project": {}, + "project_lite": {}, + "facility": { + "id": "8e6470b3-b75e-47d1-bb93-45b225750975", + "name": "Amsterdam, NL", + "code": "ams1", + "features": [ + "baremetal", + "storage", + "global_ipv4", + "backend_transfer", + "layer_2" + ], + "address": { + "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5" + }, + "ip_ranges": [ + "2604:1380:2000::/36", + "147.75.204.0/23", + "147.75.100.0/22", + "147.75.80.0/22", + "147.75.32.0/23" + ] + }, + "assigned_to": { + "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17" + }, + "interface": { + "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44" + }, + "network": "147.75.84.94", + "address": "147.75.84.95", + "gateway": "147.75.84.94", + "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1" +}, +{ + "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be2", + "address_family": 4, + "netmask": "255.255.255.254", + "created_at": "2019-07-18T08:46:38Z", + "details": null, + "tags": [], + "public": true, + "cidr": 31, + "management": true, + "manageable": true, + "enabled": true, + "global_ip": null, + "customdata": {}, + "project": {}, + "project_lite": {}, + "facility": { + "id": "8e6470b3-b75e-47d1-bb93-45b225750975", + "name": "Amsterdam, NL", + "code": "ams1", + "features": [ + "baremetal", + "storage", + "global_ipv4", + "backend_transfer", + "layer_2" + ], + "address": { + "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5" + }, + "ip_ranges": [ + "2604:1380:2000::/36", + "147.75.204.0/23", + "147.75.100.0/22", + "147.75.80.0/22", + "147.75.32.0/23" + ] + }, + "assigned_to": { + "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17" + }, + "interface": { + "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44" + }, + "network": "147.75.84.94", + "address": "147.75.84.95", + "gateway": "147.75.84.94", + "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1" +}]} \ No newline at end of file diff --git a/test/fixtures/get_projects_438659f1_ips.json b/test/fixtures/get_projects_438659f1_ips.json new file mode 100644 index 0000000..bf19a3e --- /dev/null +++ b/test/fixtures/get_projects_438659f1_ips.json @@ -0,0 +1,77 @@ +{"ip_addresses": [{ + "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be1", + "address_family": 4, + "netmask": "255.255.255.254", + "created_at": "2019-07-18T08:46:38Z", + "details": null, + "tags": [], + "public": true, + "cidr": 31, + "management": true, + "manageable": true, + "enabled": true, + "global_ip": null, + "customdata": {}, + "project": {}, + "project_lite": {}, + "facility": null, + "assigned_to": { + "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17" + }, + "interface": { + "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44" + }, + "network": "147.75.84.94", + "address": "147.75.84.95", + "gateway": "147.75.84.94", + "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1" +}, +{ + "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be2", + "address_family": 4, + "netmask": "255.255.255.254", + "created_at": "2019-07-18T08:46:38Z", + "details": null, + "tags": [], + "public": true, + "cidr": 31, + "management": true, + "manageable": true, + "enabled": true, + "global_ip": null, + "customdata": {}, + "project": {}, + "project_lite": {}, + "facility": { + "id": "8e6470b3-b75e-47d1-bb93-45b225750975", + "name": "Amsterdam, NL", + "code": "ams1", + "features": [ + "baremetal", + "storage", + "global_ipv4", + "backend_transfer", + "layer_2" + ], + "address": { + "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5" + }, + "ip_ranges": [ + "2604:1380:2000::/36", + "147.75.204.0/23", + "147.75.100.0/22", + "147.75.80.0/22", + "147.75.32.0/23" + ] + }, + "assigned_to": { + "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17" + }, + "interface": { + "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44" + }, + "network": "147.75.84.94", + "address": "147.75.84.95", + "gateway": "147.75.84.94", + "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1" +}]} \ No newline at end of file diff --git a/test/test_packet.py b/test/test_packet.py index 7c52e94..83996d7 100644 --- a/test/test_packet.py +++ b/test/test_packet.py @@ -25,6 +25,13 @@ def test_list_facilities(self): repr(facility) self.assertIsInstance(facility, packet.Facility) + def test_list_metros(self): + metros = self.manager.list_metros() + for metro in metros: + str(metro) + repr(metro) + self.assertIsInstance(metro, packet.Metro) + def test_list_plans(self): plans = self.manager.list_plans() for plan in plans: @@ -222,6 +229,18 @@ def test_list_device_ips(self): ips = self.manager.list_device_ips("e123s") self.assertIsNotNone(ips) + def test_list_projects_ips(self): + ips = self.manager.list_project_ips("438659f0") + self.assertIsNotNone(ips) + for ip in ips: + self.assertIsInstance(ip.facility, packet.Facility) + + def test_list_projects_ips_state_all(self): + ips = self.manager.list_project_ips("438659f1", params={"state": "all"}) + self.assertIsNotNone(ips) + self.assertIsNone(ips[0].facility) + self.assertIsInstance(ips[1].facility, packet.Facility) + class PacketMockManager(packet.Manager): def call_api(self, method, type="GET", params=None):