From 304ea48fa29fa30349ef673e96efae9dda367f4e Mon Sep 17 00:00:00 2001 From: Nurfet Becirevic Date: Mon, 12 Aug 2019 23:42:37 +0200 Subject: [PATCH 1/6] Added missing features and live tests: batches, devices, emails, events, ips, organizations, ports, vlans, volumes and VPN --- .gitignore | 2 + README.md | 201 +++++++- packet/BGPConfig.py | 25 + packet/BGPSession.py | 22 + packet/Batch.py | 32 ++ packet/Device.py | 84 +++- packet/DeviceBatch.py | 19 + packet/Email.py | 28 ++ packet/Event.py | 20 + packet/Facility.py | 15 +- packet/IPAddress.py | 42 ++ packet/Manager.py | 466 ++++++++++++++++-- packet/Organization.py | 78 +++ packet/Plan.py | 2 +- packet/Provider.py | 22 + packet/Snapshot.py | 22 + packet/Vlan.py | 53 ++ packet/Volume.py | 75 ++- packet/__init__.py | 8 + packet/baseapi.py | 3 +- test/fixtures/get_devices_e123s_ips.json | 151 ++++++ test/fixtures/get_ips_e123s.json | 49 ++ .../get_projects_1234_bgp-config.json | 17 + test/test_batch.py | 63 +++ test/test_device.py | 76 +++ test/test_email.py | 32 ++ test/test_event.py | 39 ++ test/test_ips.py | 67 +++ test/test_organization.py | 42 ++ test/test_packet.py | 53 +- test/test_ports.py | 76 +++ test/test_vlan.py | 62 +++ test/test_volume.py | 105 ++++ test/test_vpn.py | 25 + tox.ini | 3 + 35 files changed, 1987 insertions(+), 92 deletions(-) create mode 100644 packet/BGPConfig.py create mode 100644 packet/BGPSession.py create mode 100644 packet/Batch.py create mode 100644 packet/DeviceBatch.py create mode 100644 packet/Email.py create mode 100644 packet/Event.py create mode 100644 packet/IPAddress.py create mode 100644 packet/Organization.py create mode 100644 packet/Provider.py create mode 100644 packet/Snapshot.py create mode 100644 packet/Vlan.py create mode 100644 test/fixtures/get_devices_e123s_ips.json create mode 100644 test/fixtures/get_ips_e123s.json create mode 100644 test/fixtures/get_projects_1234_bgp-config.json create mode 100644 test/test_batch.py create mode 100644 test/test_device.py create mode 100644 test/test_email.py create mode 100644 test/test_event.py create mode 100644 test/test_ips.py create mode 100644 test/test_organization.py create mode 100644 test/test_ports.py create mode 100644 test/test_vlan.py create mode 100644 test/test_volume.py create mode 100644 test/test_vpn.py diff --git a/.gitignore b/.gitignore index 72364f9..612d8fd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ # C extensions *.so +.idea/ # Distribution / packaging .Python @@ -23,6 +24,7 @@ var/ *.egg-info/ .installed.cfg *.egg +enable/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/README.md b/README.md index 3af5a0a..7b264d9 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,35 @@ -Packet -====== +# Packet A Python client for the Packet API. ![travis build status badge](https://travis-ci.org/packethost/packet-python.svg?branch=master "Build Status") -Installation ------------- +## Table of Contents + +* [Installation](#installation) +* [Documentation](#documentation) +* [Authentication](#authentication) +* [Examples](#examples) + * [List Projects](#list-projects) + * [List Plans](#list-plans) + * [Creating a Device](#creating-a-device) + * [Checking the Status and Rebooting a Device](#checking-the-status-and-rebooting-a-device) + * [Listing all Devices Limiting to 50 per Page](#listing-all-devices-limiting-to-50-per-page) + * [Updating a Device](#updating-a-device) + * [Deleting a Device](#deleting-a-device) + * [Creating a Device Batch](#creating-a-device-batch) + * [Creating a Volume](#creating-a-volume) + * [Attaching and Detaching a Volume](#attaching-and-detaching-a-volume) + * [Creating and Restoring a Volume Snapshot](#creating-and-restoring-a-volume-snapshot) + * [Listing Project IP Addresses](#listing-project-ip-addresses) + * [Creating a Project for an Organization](#creating-a-project-for-an-organization) + * [Creating a VLAN](#creating-a-vlan) +* [Contributing](#contributing) +* [Copyright](#copyright) +* [Changes](#changes) + +## Installation + The packet python api library can be installed using pip: pip install packet-python @@ -15,14 +38,23 @@ Package information available here: https://pypi.python.org/pypi/packet-python -Documentation -------------- +## Documentation + Full Packet API documenation is available here: [https://www.packet.net/developers/api/](https://www.packet.net/developers/api/) -Examples --------- -### List projects +## Authentication + +Provide your credentials when instantiating client: + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") +``` + +## Examples + +### List Projects ```python import packet @@ -33,7 +65,7 @@ for project in projects: print(project) ``` -### List plans +### List Plans ```python import packet @@ -55,11 +87,11 @@ 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', - operating_system='ubuntu_14_04') + operating_system='ubuntu_18_04') print(device) ``` -### Checking the status and rebooting a Device +### Checking the Status and Rebooting a Device ```python import packet @@ -70,7 +102,7 @@ print(device.state) device.reboot() ``` -### Listing all devices, limiting to 50 per page +### Listing all Devices Limiting to 50 per Page _Packet API defaults to a limit of 10 per page_ @@ -84,8 +116,138 @@ devices = manager.list_devices(project_id='project_id', params = params) print(devices) ``` -Contributing ------------- +### Updating a Device + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +device = manager.get_device('device-id') +device.hostname = "test02" +device.description = "new description" + +device.update() +``` + +### Deleting a Device + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +device = manager.get_device('device-id') +device.delete() +``` + +### Creating a Device Batch + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +batch01 = packet.DeviceBatch({ + "hostname": "batch01", + "quantity": 2, + "facility": "ams1", + "operating_system": "centos_7", + "plan": "baremetal_0", + }) + +device_batch = manager.create_batch(project_id="project_id", params=[batch01]) +print(device_batch) +``` + +### Creating a Volume + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +volume = manager.create_volume(project_id="project-id", + description="volume description", + plan="storage_1", + size="100", + facility="ewr1", + snapshot_count=7, + snapshot_frequency="1day") +print(volume) +``` + +### Attaching and Detaching a Volume + +```python +import packet +import time + +manager = packet.Manager(auth_token="yourapiauthtoken") +volume = manager.get_volume("volume_id") + +volume.attach("device_id") + +while True: + if manager.get_device("device_id").state == "active": + break + time.sleep(2) + +volume.detach() +``` + +## Creating and Restoring a Volume Snapshot + +```python +import packet +import time + +manager = packet.Manager(auth_token="yourapiauthtoken") + +volume = manager.get_volume("volume_id") +volume.create_snapshot() + +while True: + if manager.get_volume(volume.id).state == "active": + break + time.sleep(2) + +snapshots = manager.get_snapshots(volume.id) +volume.restore(snapshots[0].timestamp) +``` + +### Listing Project IP Addresses + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +ips = manager.list_project_ips("project_id") +for ip in ips: + print(ip.address) +``` + +### Creating a Project for an Organization + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +project = manager.create_organization_project( + org_id="organization_id", + name="Integration Tests", + customdata={"tag": "QA"} +) +print(project) +``` + +### Creating a VLAN + +```python +import packet +manager = packet.Manager(auth_token="yourapiauthtoken") + +vlan = manager.create_vlan(project_id="project_id", facility="ewr1") +print(vlan) +``` + +## Contributing * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. @@ -94,18 +256,15 @@ Contributing * Commit and push until you are happy with your contribution. * You can test your changes with the `test/tests.sh` script, which is what travis uses to check builds. -Credits -------- +## Credits CargoCulted with much gratitude from: https://github.com/koalalorenzo/python-digitalocean -Copyright ---------- +## Copyright Copyright (c) 2017 Packet Host. See [License](LICENSE.txt) for further details. -Changes -------- +## Changes See the [Changelog](CHANGELOG.md) for further details. diff --git a/packet/BGPConfig.py b/packet/BGPConfig.py new file mode 100644 index 0000000..ecb58f9 --- /dev/null +++ b/packet/BGPConfig.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class BGPConfig: + def __init__(self, data): + self.id = data["id"] + self.status = data["status"] + self.deployment_type = data["deployment_type"] + self.asn = data["asn"] + self.md5 = data["md5"] + self.route_object = data["route_object"] + self.max_prefix = data["max_prefix"] + self.created_at = data["created_at"] + self.requested_at = data["requested_at"] + self.project = data["project"] + self.sessions = data["sessions"] + self.ranges = data["ranges"] + self.href = data["href"] + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/BGPSession.py b/packet/BGPSession.py new file mode 100644 index 0000000..16b5735 --- /dev/null +++ b/packet/BGPSession.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class BGPSession: + def __init__(self, data): + self.id = data["id"] + self.status = data["status"] + self.learned_routes = data["learned_routes"] + self.switch_name = data["switch_name"] + self.default_route = data["default_route"] + self.created_at = data["created_at"] + self.updated_at = data["updated_at"] + self.device = data["device"] + self.address_family = data["address_family"] + self.href = data["href"] + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Batch.py b/packet/Batch.py new file mode 100644 index 0000000..6a6fa54 --- /dev/null +++ b/packet/Batch.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Batch: + def __init__(self, data): + if data["id"] is not None: + self.id = data["id"] + if data["error_messages"] is not None: + self.error_messages = data["error_messages"] + if data["quantity"] is not None: + self.quantity = data["quantity"] + if data["state"] is not None: + self.state = data["state"] + if data["created_at"] is not None: + self.created_at = data["created_at"] + if data["updated_at"] is not None: + self.updated_at = data["updated_at"] + if data["devices"] is not None: + self.devices = data["devices"] + if data["project"] is not None: + self.project = data["project"] + if data["state"] is not None: + self.state = data["state"] + if data["error_messages"] is not None: + self.error_messages = data["error_messages"] + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Device.py b/packet/Device.py index 65cc0b1..51bf8d3 100644 --- a/packet/Device.py +++ b/packet/Device.py @@ -1,34 +1,80 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: LGPL-3.0-only - -from .OperatingSystem import OperatingSystem +# from .Volume import Volume class Device: def __init__(self, data, manager): self.manager = manager + self.id = data["id"] + self.short_id = data["short_id"] + self.hostname = data["hostname"] + self.description = data["description"] + self.state = data["state"] + self.tags = data["tags"] + if "image_url" in data: + self.image_url = data["image_url"] self.billing_cycle = data["billing_cycle"] + self.user = data["user"] + self.iqn = data["iqn"] + self.locked = data["locked"] + self.bonding_mode = data["bonding_mode"] self.created_at = data["created_at"] + self.updated_at = data["updated_at"] + if "ipxe_script_url" in data: + self.ipxe_script_url = data["ipxe_script_url"] + else: + self.ipxe_script_url = None + if "always_pxe" in data: + self.always_pxe = data["always_pxe"] + else: + self.always_pxe = False + if "storage" in data: + self.storage = data["storage"] + if "customdata" in data: + self.customdata = data["customdata"] + else: + self.customdata = None + self.operating_system = data["operating_system"] self.facility = data["facility"] - self.hostname = data["hostname"] - self.href = data["href"] - self.id = data["id"] + self.project = data["project"] + if "ssh_keys" in data: + self.ssh_keys = data["ssh_keys"] + if "project_lite" in data: + self.project_lite = data["project_lite"] + + if "volumes" in data: + self.volumes = data["volumes"] + self.ip_addresses = data["ip_addresses"] - self.locked = data["locked"] - self.operating_system = OperatingSystem(data["operating_system"]) self.plan = data["plan"] - self.spot_instance = data.get("spot_instance") - self.spot_price_max = data.get("spot_price_max") - self.ssh_keys = data.get("ssh_keys", []) - self.state = data["state"] - self.tags = data["tags"] - self.termination_time = data.get("termination_time") - self.updated_at = data["updated_at"] - self.user = data["user"] + self.userdata = data["userdata"] + if "switch_uuid" in data: + self.switch_uuid = data["switch_uuid"] + if "network_ports" in data: + self.network_ports = data["network_ports"] + self.href = data["href"] + if "spot_instance" in data: + self.spot_instance = data["spot_instance"] + else: + self.spot_instance = False + if "root_password" in data: + self.root_password = data["root_password"] def update(self): - params = {"hostname": self.hostname, "locked": self.locked, "tags": self.tags} + params = { + "hostname": self.hostname, + "locked": self.locked, + "tags": self.tags, + "description": self.description, + "billing_cycle": self.billing_cycle, + "userdata": self.userdata, + "always_pxe": self.always_pxe, + "ipxe_script_url": self.ipxe_script_url, + "spot_instance": self.spot_instance, + "customdata": self.customdata + } return self.manager.call_api( "devices/%s" % self.id, type="PATCH", params=params @@ -55,8 +101,14 @@ def reboot(self): "devices/%s/actions" % self.id, type="POST", params=params ) + def ips(self): + return self.manager.list_device_ips(self.id) + def __str__(self): return "%s" % self.hostname def __repr__(self): return "{}: {}".format(self.__class__.__name__, self.id) + + def __getitem__(self, item): + return getattr(self, item) diff --git a/packet/DeviceBatch.py b/packet/DeviceBatch.py new file mode 100644 index 0000000..6c474a5 --- /dev/null +++ b/packet/DeviceBatch.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class DeviceBatch: + def __init__(self, data): + if "hostname" in data: + self.hostname = data["hostname"] + if "plan" in data: + self.plan = data["plan"] + if "operating_system" in data: + self.operating_system = data["operating_system"] + if "facility" in data: + self.facility = data["facility"] + if "quantity" in data: + self.quantity = data["quantity"] + + def __str__(self): + return "%s" % self.hostname diff --git a/packet/Email.py b/packet/Email.py new file mode 100644 index 0000000..ec6f1c0 --- /dev/null +++ b/packet/Email.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Email: + def __init__(self, data, manager): + self.manager = manager + + self.id = data["id"] + self.address = data["address"] + self.default = data["default"] + self.verified = data["verified"] + + def update(self): + params = {"address": self.address, "default": self.default} + + return self.manager.call_api( + "emails/%s" % self.id, type="PATCH", params=params + ) + + def delete(self): + return self.manager.call_api("emails/%s" % self.id, type="DELETE") + + def __str__(self): + return "%s" % self.address + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Event.py b/packet/Event.py new file mode 100644 index 0000000..1a34337 --- /dev/null +++ b/packet/Event.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Event: + def __init__(self, data): + self.id = data["id"] + self.type = data["type"] + self.body = data["body"] + self.state = data["state"] + self.created_at = data["created_at"] + self.modified_by = data["modified_by"] + self.ip = data["ip"] + self.interpolated = data["interpolated"] + + def __str__(self): + return "%s" % self.interpolated + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Facility.py b/packet/Facility.py index 0aa0746..f5e0a61 100644 --- a/packet/Facility.py +++ b/packet/Facility.py @@ -4,11 +4,16 @@ class Facility: def __init__(self, data): - self.id = data["id"] - self.code = data["code"] - self.name = data["name"] - self.features = data["features"] - self.address = data["address"] + if "id" in data: + self.id = data["id"] + if "code" in data: + self.code = data["code"] + if "name" in data: + self.name = data["name"] + if "features" in data: + self.features = data["features"] + if "address" in data: + self.address = data["address"] def __str__(self): return "%s" % self.code diff --git a/packet/IPAddress.py b/packet/IPAddress.py new file mode 100644 index 0000000..e0bb8e0 --- /dev/null +++ b/packet/IPAddress.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + +from .Facility import Facility + + +class IPAddress: + def __init__(self, data): + self.ip = data["id"] + self.address_family = data["address_family"] + self.netmask = data["netmask"] + self.created_at = data["created_at"] + self.details = data["details"] + self.tags = data["tags"] + self.public = data["public"] + self.cidr = data["cidr"] + self.management = data["management"] + self.enabled = data["enabled"] + self.global_ip = data["global_ip"] + self.customdata = data["customdata"] + self.project = data["project"] + self.project_lite = data["project_lite"] + self.facility = Facility(data["facility"]) + + if "details" in data: + self.details = data["details"] + if "assigned_to" in data: + self.assigned_to = data["assigned_to"] + if "interface" in data: + self.interface = data["interface"] + if "network" in data: + self.network = data["network"] + if "address" in data: + self.address = data["address"] + if "gateway" in data: + self.gateway = data["gateway"] + + def __str__(self): + return "%s" % self.code + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Manager.py b/packet/Manager.py index 7c1ec9b..a5fa093 100644 --- a/packet/Manager.py +++ b/packet/Manager.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- # SPDX-License-Identifier: LGPL-3.0-only - +from packet.Vlan import Vlan from .baseapi import BaseAPI from .baseapi import Error as PacketError +from .Batch import Batch from .Plan import Plan from .Device import Device from .SSHKey import SSHKey @@ -10,6 +11,14 @@ from .Facility import Facility from .OperatingSystem import OperatingSystem from .Volume import Volume +from .BGPConfig import BGPConfig +from .BGPSession import BGPSession +from .IPAddress import IPAddress +from .Snapshot import Snapshot +from .Organization import Organization +from .Email import Email +from .Event import Event +from .Provider import Provider class Manager(BaseAPI): @@ -72,26 +81,45 @@ def list_devices(self, project_id, params={}): devices.append(device) return devices + def list_all_devices(self, project_id): + raw_devices = list() + page = 1 + while True: + paginate = {"page": page} + data = self.call_api("projects/%s/devices" % project_id, params=paginate) + next = self.meta["next"] + raw_devices.extend(data["devices"]) + if next is None: + break + else: + page += 1 + + all_devices = list() + for raw_device in raw_devices: + device = Device(raw_device, self) + all_devices.append(device) + return all_devices + def create_device( - self, - project_id, - hostname, - plan, - facility, - operating_system, - always_pxe=False, - billing_cycle="hourly", - features={}, - ipxe_script_url="", - locked=False, - project_ssh_keys=[], - public_ipv4_subnet_size=31, - spot_instance=False, - spot_price_max=-1, - tags={}, - termination_time=None, - user_ssh_keys=[], - userdata="", + self, + project_id, + hostname, + plan, + facility, + operating_system, + always_pxe=False, + billing_cycle="hourly", + features={}, + ipxe_script_url="", + locked=False, + project_ssh_keys=[], + public_ipv4_subnet_size=31, + spot_instance=False, + spot_price_max=-1, + tags={}, + termination_time=None, + user_ssh_keys=[], + userdata="", ): params = { @@ -154,14 +182,14 @@ def list_volumes(self, project_id, params={}): return volumes def create_volume( - self, - project_id, - description, - plan, - size, - facility, - snapshot_count=0, - snapshot_frequency=None, + self, + project_id, + description, + plan, + size, + facility, + snapshot_count=0, + snapshot_frequency=None, ): params = { "description": description, @@ -181,6 +209,7 @@ def create_volume( type="POST", params=params, ) + return Volume(data, self) def get_volume(self, volume_id): @@ -193,7 +222,8 @@ def get_capacity(self, legacy=None): :param legacy: Indicate set of server types to include in response - Validation of `legacy` is left to the packet api to avoid going out of date if any new value is introduced. + Validation of `legacy` is left to the packet api to avoid going out + of date if any new value is introduced. The currently known values are: - only (current default, will be switched "soon") - include @@ -210,7 +240,11 @@ def validate_capacity(self, servers): params = {"servers": []} for server in servers: params["servers"].append( - {"facility": server[0], "plan": server[1], "quantity": server[2]} + { + "facility": server[0], + "plan": server[1], + "quantity": server[2] + } ) try: @@ -225,3 +259,375 @@ def validate_capacity(self, servers): def get_spot_market_prices(self, params={}): data = self.call_api("/market/spot/prices", params=params) return data["spot_market_prices"] + + # BGP Config + def get_bgp_config(self, project_id): + data = self.call_api("projects/%s/bgp-config" % project_id) + return BGPConfig(data) + + def enable_project_bgp_config(self, project_id, asn, + deployment_type, + md5=None, + use_case=None): + params = { + "deployment_type": deployment_type, + "asn": asn, + "md5": md5, + "use_case": use_case + } + self.call_api("/projects/%s/bgp-configs" % project_id, + type="POST", + params=params) + + # BGP Session + def get_bgp_sessions(self, device_id, params={}): + data = self.call_api("/devices/%s/bgp/sessions" % device_id, + type="GET", params=params) + bgp_sessions = list() + for jsoned in data["bgp_sessions"]: + bpg_session = BGPSession(jsoned) + bgp_sessions.append(bpg_session) + return bgp_sessions + + def create_bgp_session(self, device_id, address_family): + data = self.call_api("/devices/%s/bgp/sessions" % device_id, + type="POST", + params={ + "address_family": address_family + }) + return BGPSession(data) + + # IP operations + def list_device_ips(self, device_id): + data = self.call_api("devices/%s/ips" % device_id, type="GET") + ips = list() + for jsoned in data["ip_addresses"]: + ip = IPAddress(jsoned) + ips.append(ip) + return ips + + def get_ip(self, ip_id): + data = self.call_api("ips/%s" % ip_id) + return IPAddress(data) + + def delete_ip(self, ip_id): + self.call_api("ips/%s" % ip_id, type="DELETE") + + def list_project_ips(self, project_id, params={}): + data = self.call_api("projects/%s/ips" % project_id, + type="GET", + params=params) + ips = list() + for jsoned in data["ip_addresses"]: + ip = IPAddress(jsoned) + ips.append(ip) + return ips + + def get_available_ip_subnets(self, ip_id, cidr): + data = self.call_api("/ips/%s/available" % ip_id, + type="GET", params="cidr=%s" % cidr) + return data + + def create_device_ip(self, device_id, address, + manageable=True, customdata=None): + params = { + "address": address, + "manageable": manageable, + "customdata": customdata + } + + data = self.call_api("/devices/%s/ips" % device_id, + params=params, type="POST") + return IPAddress(data) + + def reserve_ip_address(self, project_id, type, quantity, facility, + details=None, comments=None, tags=list()): + request = { + "type": type, + "quantity": quantity, + "facility": facility, + "details": details, + "comments": comments, + "tags": tags + } + + data = self.call_api("/projects/%s/ips" % project_id, params=request) + ips = list() + for i in data["ip_addresses"]: + ip = IPAddress(i) + ips.append(ip) + return ips + + # Batches + def create_batch(self, project_id, params): + param = { + "batches": params + } + data = self.call_api("/projects/%s/devices/batch" % project_id, + type="POST", params=param) + batches = list() + for b in data["batches"]: + batch = Batch(b) + batches.append(batch) + return batches + + def list_batches(self, project_id, params=None): + data = self.call_api("/projects/%s/batches" % project_id, + type="GET", params=params) + batches = list() + for b in data["batches"]: + batch = Batch(b) + batches.append(batch) + return batches + + def delete_batch(self, batch_id, remove_associated_instances=False): + self.call_api("/batches/%s" % batch_id, type="DELETE", + params=remove_associated_instances) + + # Snapshots + def get_snapshots(self, volume_id, params=None): + data = self.call_api("storage/%s/snapshots" % volume_id, + type="GET", params=params) + snapshots = list() + for ss in data["snapshots"]: + snapshot = Snapshot(ss) + snapshots.append(snapshot) + + return snapshots + + def restore_volume(self, volume_id, restore_point): + params = { + "restore_point": restore_point + } + self.call_api("storage/%s/restore" % volume_id, + type="POST", params=params) + + # Organization + def list_organizations(self, params=None): + data = self.call_api("organizations", type="GET", params=params) + orgs = list() + for org in data["organizations"]: + o = Organization(org) + orgs.append(o) + + return orgs + + def get_organization(self, org_id, params=None): + data = self.call_api("organizations/%s" % org_id, + type="GET", params=params) + return Organization(data) + + def list_organization_projects(self, org_id, params=None): + data = self.call_api("organizations/%s/projects" % org_id, + type="GET", params=params) + projects = list() + for p in data["projects"]: + projects.append(Project(p, self)) + + return projects + + def list_organization_devices(self, org_id, params=None): + data = self.call_api("organizations/%s/devices" % org_id, + type="GET", params=params) + devices = list() + for d in data["devices"]: + devices.append(Device(d, self)) + + return devices + + def create_organization_project(self, org_id, name, payment_method_id=None, customdata=None): + params = { + "name": name, + "payment_method_id": payment_method_id, + "customdata": customdata, + } + data = self.call_api("organizations/%s/projects" % org_id, type="POST", + params=params) + return Project(data, self) + + # Email + def add_email(self, address, default=False): + params = {"address": address, "default": default} + data = self.call_api("emails", type="POST", params=params) + return Email(data, self) + + def get_email(self, email_id): + data = self.call_api("emails/%s" % email_id) + return Email(data, self) + + # Event + def list_events(self, params=None): + data = self.call_api("events", type="GET", params=params) + events = list() + for e in data["events"]: + events.append(Event(e)) + + return events + + def get_event(self, event_id): + data = self.call_api("events/%s" % event_id) + return Event(data) + + def list_device_events(self, device_id, params=None): + data = self.call_api("devices/%s/events" % device_id, + type="GET", params=params) + events = list() + for e in data["events"]: + events.append(Event(e)) + + return events + + def list_project_events(self, project_id, params=None): + data = self.call_api("projects/%s/events" % project_id, + type="GET", params=params) + events = list() + for e in data["events"]: + events.append(Event(e)) + + return events + + def get_volume_events(self, volume_id, params=None): + data = self.call_api("volumes/%s/events" % volume_id, + type="GET", params=params) + events = list() + for e in data["events"]: + events.append(Event(e)) + + return events + + # vlan operations + def list_vlans(self, project_id, params=None): + data = self.call_api("projects/%s/virtual-networks" % project_id, + type="GET", + params=params) + vlans = list() + for vlan in data["virtual_networks"]: + vlans.append(Vlan(vlan, self)) + + return vlans + + def create_vlan(self, project_id, facility, vxlan=None, vlan=None): + params = { + "project_id": project_id, + "facility": facility, + "vxlan": vxlan, + "vlan": vlan, + + } + data = self.call_api("projects/%s/virtual-networks" % project_id, + type="POST", + params=params) + return Vlan(data, self) + + def assign_port(self, port_id, vlan_id): + params = { + "vnid": vlan_id, + } + self.call_api("ports/%s/assign" % port_id, type="POST", + params=params) + + def remove_port(self, port_id, vlan_id): + params = { + "vnid": vlan_id, + } + self.call_api("ports/%s/unassign" % port_id, type="POST", + params=params) + + def disbond_ports(self, port_id, bulk_disable): + params = { + "bulk_disable": bulk_disable, + } + self.call_api("ports/%s/disbond" % port_id, type="POST", + params=params) + + def bond_ports(self, port_id, bulk_disable): + params = { + "bulk_disable": bulk_disable, + } + self.call_api("ports/%s/bond" % port_id, type="POST", + params=params) + + def convert_layer_2(self, port_id, vlan_id): + params = { + "vnid": vlan_id, + } + self.call_api("ports/%s/convert/layer-2" % port_id, type="POST", + params=params) + + def convert_layer_3(self, port_id, request_ips): + params = { + "request_ips": request_ips, + } + self.call_api("ports/%s/convert/layer-3" % port_id, type="POST", + params=params) + + def assign_native_vlan(self, port_id, vnid): + params = { + "vnid": vnid, + } + self.call_api("ports/%s/native-vlan" % port_id, type="POST", + params=params) + + def remove_native_vlan(self, port_id): + self.call_api("ports/%s/native-vlan" % port_id, type="DELETE") + + def get_vpn_configuration(self, facilityCode): + params = {"code": facilityCode} + data = self.call_api("user/vpn", type="GET", params=params) + return data + + def turn_on_vpn(self): + return self.call_api("user/vpn", type="POST") + + def turn_off_vpn(self): + return self.call_api("user/vpn", type="DELETE") + + # Packet connect + def create_packet_connections(self, params): + body = { + "name": params["name"], + "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"] + + data = self.call_api("/packet-connect/connections", + type="POST", params=body) + return data + + def get_packet_connection(self, connection_id): + data = self.call_api("/packet-connect/connections/%s" % connection_id) + return data + + def delete_packet_connection(self, connection_id): + data = self.call_api("/packet-connect/connections/%s" % connection_id, type="DELETE") + return data + + def provision_packet_connection(self, connection_id): + data = self.call_api("/packet-connect/connections/{id}/provision" % connection_id, type="POST") + return data + + def deprovision_packet_connection(self, connection_id, delete): + params = { + "delete": delete + } + data = self.call_api("/packet-connect/connections/{id}/deprovision" % connection_id, type="POST", params=params) + return data + + def list_packet_connect_providers(self): + data = self.call_api("/packet-connect/providers", type="GET") + providers = list() + for p in data["providers"]: + providers.append(Provider(p)) + return providers + + def get_packet_connect_provider(self, provider_id): + data = self.call_api("/packet-connect/providers/%s" % provider_id, type="GET") + return Provider(data) diff --git a/packet/Organization.py b/packet/Organization.py new file mode 100644 index 0000000..1ada1d0 --- /dev/null +++ b/packet/Organization.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Organization: + def __init__(self, data): + if "id" in data: + self.id = data["id"] + if "name" in data: + self.name = data["name"] + if "description" in data: + self.description = data["description"] + if "website" in data: + self.website = data["website"] + if "twitter" in data: + self.twitter = data["twitter"] + if "created_at" in data: + self.created_at = data["created_at"] + if "updated_at" in data: + self.updated_at = data["updated_at"] + if "tax_id" in data: + self.tax_id = data["tax_id"] + if "main_phone" in data: + self.main_phone = data["main_phone"] + if "billing_phone" in data: + self.billing_phone = data["billing_phone"] + if "credit_amount" in data: + self.credit_amount = data["credit_amount"] + if "personal" in data: + self.personal = data["personal"] + if "customdata" in data: + self.customdata = data["customdata"] + if "attn" in data: + self.attn = data["attn"] + if "purchase_order" in data: + self.purchase_order = data["purchase_order"] + if "billing_name" in data: + self.billing_name = data["billing_name"] + if "enforce_2fa" in data: + self.enforce_2fa = data["enforce_2fa"] + if "enforce_2fa_at" in data: + self.enforce_2fa_at = data["enforce_2fa_at"] + if "short_id" in data: + self.short_id = data["short_id"] + if "account_id" in data: + self.account_id = data["account_id"] + if "enabled_features" in data: + self.enabled_features = data["enabled_features"] + if "maintenance_email"in data: + self.maintenance_email = data["maintenance_email"] + if "abuse_email" in data: + self.abuse_email = data["abuse_email"] + if "address" in data: + self.address = data["address"] + if "billing_address" in data: + self.billing_address = data["billing_address"] + if "account_manager" in data: + self.account_manager = data["account_manager"] + if "logo" in data: + self.logo = data["logo"] + if "logo_thumb" in data: + self.logo_thumb = data["logo_thumb"] + if "projects" in data: + self.projects = data["projects"] + if "plan" in data: + self.plan = data["plan"] + if "monthly_spend" in data: + self.monthly_spend = data["monthly_spend"] + if "current_user_abilities" in data: + self.current_user_abilities = data["current_user_abilities"] + if "href" in data: + self.href = data["href"] + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Plan.py b/packet/Plan.py index 0ade108..e2cc0c4 100644 --- a/packet/Plan.py +++ b/packet/Plan.py @@ -13,7 +13,7 @@ def __init__(self, data): self.description = data["description"] def __str__(self): - return "%s" % (self.slug) + return "%s" % self.slug def __repr__(self): return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Provider.py b/packet/Provider.py new file mode 100644 index 0000000..861742e --- /dev/null +++ b/packet/Provider.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Provider: + def __init__(self, data): + if data["id"] is not None: + self.id = data["id"] + if data["name"] is not None: + self.name = data["name"] + if data["status"] is not None: + self.status = data["status"] + if data["type"] is not None: + self.type = data["type"] + if data["public"] is not None: + self.public = data["public"] + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Snapshot.py b/packet/Snapshot.py new file mode 100644 index 0000000..9009b61 --- /dev/null +++ b/packet/Snapshot.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only + + +class Snapshot: + def __init__(self, data): + if "id" in data: + self.id = data["id"] + if "status" in data: + self.status = data["status"] + if "timestamp" in data: + self.timestamp = data["timestamp"] + if "created_at" in data: + self.created_at = data["created_at"] + if "volume" in data: + self.volume = data["volume"] + + def __str__(self): + return "%s" % self.name + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Vlan.py b/packet/Vlan.py new file mode 100644 index 0000000..9c0b8a7 --- /dev/null +++ b/packet/Vlan.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# SPDX-License-Identifier: LGPL-3.0-only +from packet import Project +from .Facility import Facility + + +class Vlan: + def __init__(self, data, manager): + self.manager = manager + if data is None: + return + + self.id = data["id"] + self.description = data["description"] + self.vxlan = data["vxlan"] + self.internet_gateway = data["internet_gateway"] + self.facility_code = data["facility_code"] + self.created_at = data["created_at"] + + self.facility = Facility(data["facility"]) + try: + project_data = self.manager.call_api(data["assigned_to"]["href"], type="GET") + self.assigned_to = Project(project_data, self.manager) + except (KeyError, IndexError): + self.attached_to = None + + def get(self): + return self.manager.call_api("virtual-networks/%s" % self.id, type="GET") + + def delete(self): + return self.manager.call_api("virtual-networks/%s" % self.id, type="DELETE") + + def create_internet_gateway(self, ip_reservation_length): + """:param ip_reservation_length: (required) number of IP addresses possible 8 or 16""" + params = {"length": ip_reservation_length} + return self.manager.call_api( + "/virtual-networks/%s/internet-gateways" % self.id, type="POST", params=params + ) + + def assign_native_vlan(self, port_id): + params = {"vnid": self.id} + return self.manager.call_api( + "/ports/%s/native-vlan" % port_id, type="POST", params=params + ) + + def remove_native_vlan(self, port_id): + return self.manager.call_api("/ports/%s/native-vlan" % port_id, type="DELETE") + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Volume.py b/packet/Volume.py index 353dc0f..c6e52f1 100644 --- a/packet/Volume.py +++ b/packet/Volume.py @@ -8,6 +8,8 @@ class Volume: def __init__(self, data, manager): self.manager = manager + if data is None: + return self.id = data["id"] self.name = data["name"] @@ -61,10 +63,33 @@ def list_snapshots(self, params={}): return snapshots def create_snapshot(self): - return self.manager.call_api("storage/%s/snapshots" % self.id, type="POST") - - def create_clone(self): - return self.manager.call_api("storage/%s/clone" % self.id, type="POST") + self.manager.call_api("storage/%s/snapshots" % self.id, type="POST") + + def create_snapshot_policy(self, frequency, count): + """Creates a new snapshot policy of your volume. + + :param frequency: (required) Snapshot frequency + + Validation of `frequency` is left to the packet api to avoid going out + of date if any new value is introduced. + The currently known values are: + - 1hour, + - 1day + - 1week + - 1month + - 1year + """ + data = self.manager.call_api("storage/{0}/snapshot-policies?snapshot_frequency={1}&snapshot_count={2}".format( + self.id, + frequency, count), + type="POST") + return SnapshotPolicy(data, self) + + def clone(self): + return Volume(self.manager.call_api("storage/%s/clone" % self.id, type="POST"), manager=self.manager) + + def restore(self, restore_point): + self.manager.restore_volume(self.id, restore_point=restore_point) def __str__(self): return "%s" % self.id @@ -92,3 +117,45 @@ def __str__(self): def __repr__(self): return "{}: {}".format(self.__class__.__name__, self.id) + + +class SnapshotPolicy: + def __init__(self, data, policy): + self.policy = policy + + self.id = data["id"] + self.count = data["snapshot_count"] + self.frequency = data["snapshot_frequency"] + self.created_at = data["created_at"] + self.updated_at = data["updated_at"] + + def delete(self): + return self.policy.manager.call_api( + "storage/snapshot-policies/%s" % self.id, type="DELETE" + ) + + def update_snapshot_policy(self, frequency, count): + """Updates the volume snapshot policy. + + :param frequency: (required) Snapshot frequency + + Validation of `frequency` is left to the packet api to avoid going out + of date if any new value is introduced. + The currently known values are: + - 1hour, + - 1day + - 1week + - 1month + - 1year + """ + data = self.policy.manager.call_api( + "storage/snapshot-policies/{0}?snapshot_frequency={1}&snapshot_count={2}".format( + self.id, frequency, count), + type="PATCH") + return SnapshotPolicy(data, self) + + def __str__(self): + return "%s" % self.id + + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/__init__.py b/packet/__init__.py index 2427a96..093923b 100644 --- a/packet/__init__.py +++ b/packet/__init__.py @@ -10,11 +10,19 @@ __copyright__ = "Copyright (c) 2019, Packet" from .Device import Device # noqa +from .Email import Email # noqa +from .Event import Event # noqa from .Facility import Facility # noqa from .OperatingSystem import OperatingSystem # noqa from .Plan import Plan # noqa from .Project import Project # noqa from .SSHKey import SSHKey # noqa from .Volume import Volume # noqa +from .BGPConfig import BGPConfig # noqa +from .BGPSession import BGPSession # noqa +from .DeviceBatch import DeviceBatch # noqa from .Manager import Manager # noqa +from .Snapshot import Snapshot # noqa +from .Organization import Organization # noqa +from .Provider import Provider # noqa from .baseapi import Error # noqa diff --git a/packet/baseapi.py b/packet/baseapi.py index 54cac3d..c0ceada 100644 --- a/packet/baseapi.py +++ b/packet/baseapi.py @@ -54,7 +54,8 @@ def call_api(self, method, type="GET", params=None): # noqa url = url + "%s" % self._parse_params(params) resp = requests.get(url, headers=headers) elif type == "POST": - resp = requests.post(url, headers=headers, data=json.dumps(params)) + resp = requests.post(url, headers=headers, data=json.dumps(params, default=lambda o: o.__dict__, + sort_keys=True, indent=4)) elif type == "DELETE": resp = requests.delete(url, headers=headers) elif type == "PATCH": diff --git a/test/fixtures/get_devices_e123s_ips.json b/test/fixtures/get_devices_e123s_ips.json new file mode 100644 index 0000000..87f6ffa --- /dev/null +++ b/test/fixtures/get_devices_e123s_ips.json @@ -0,0 +1,151 @@ +{ + "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": "06fd360b-8f6c-4f3c-a858-6ea8a9bc7cf8", + "address_family": 6, + "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", + "created_at": "2019-07-18T08:46:37Z", + "details": null, + "tags": [], + "public": true, + "cidr": 127, + "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": "2604:1380:2000:ae00::", + "address": "2604:1380:2000:ae00::1", + "gateway": "2604:1380:2000:ae00::", + "href": "/ips/06fd360b-8f6c-4f3c-a858-6ea8a9bc7cf8" + }, + { + "id": "4edf9211-142e-476e-b926-ebd247fb7b66", + "address_family": 4, + "netmask": "255.255.255.254", + "created_at": "2019-07-18T08:46:37Z", + "details": null, + "tags": [], + "public": false, + "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": "10.80.110.0", + "address": "10.80.110.1", + "gateway": "10.80.110.0", + "href": "/ips/4edf9211-142e-476e-b926-ebd247fb7b66" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/get_ips_e123s.json b/test/fixtures/get_ips_e123s.json new file mode 100644 index 0000000..6eeb5b8 --- /dev/null +++ b/test/fixtures/get_ips_e123s.json @@ -0,0 +1,49 @@ +{ + "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" +} \ No newline at end of file diff --git a/test/fixtures/get_projects_1234_bgp-config.json b/test/fixtures/get_projects_1234_bgp-config.json new file mode 100644 index 0000000..c9161f1 --- /dev/null +++ b/test/fixtures/get_projects_1234_bgp-config.json @@ -0,0 +1,17 @@ +{ + "id": "913317a6-8baf-4142-94be-87de68474947", + "status": "enabled", + "deployment_type": "local", + "asn": 65000, + "md5": "c3RhY2twb2ludDIwMTgK", + "route_object": null, + "max_prefix": 10, + "created_at": "2018-04-05T20:13:17Z", + "requested_at": "2018-07-12T15:38:49Z", + "project": { + "href": "/projects/93125c2a-8b78-4d4f-a3c4-7367d6b7cca8" + }, + "sessions": [], + "ranges": [], + "href": "/bgp-configs/913317a6-8baf-4142-94be-87de68474947" +} diff --git a/test/test_batch.py b/test/test_batch.py new file mode 100644 index 0000000..9795393 --- /dev/null +++ b/test/test_batch.py @@ -0,0 +1,63 @@ +import os +import sys +import time +import unittest +import packet + +from datetime import datetime + + +class TestBatches(unittest.TestCase): + @classmethod + def setUpClass(self): + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + org_id = self.manager.list_organizations()[0].id + self.project = self.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-Batch_{}".format(datetime.utcnow().timestamp()) + ) + self.batches = list() + + def test_create_batch(self): + params = list() + batch01 = packet.DeviceBatch({ + "hostname": "batchtest01", + "quantity": 1, + "facility": "ewr1", + "operating_system": "centos_7", + "plan": "baremetal_0", + }) + + params.append(batch01) + data = self.manager.create_batch(project_id=self.project.id, + params=params) + self.batches = data + time.sleep(10) + + def test_list_batches(self): + self.manager.list_batches(project_id=self.project.id) + + def test_delete_batches(self): + self.batches = self.manager.list_batches(project_id=self.project.id) + for batch in self.batches: + self.manager.delete_batch(batch.id, + remove_associated_instances=True) + + @classmethod + def tearDownClass(self): + devices = self.manager.list_devices(project_id=self.project.id) + for device in devices: + if device.hostname == "batchtest01": + if device.state != "active": + while True: + if self.manager \ + .get_device(device.id).state != "active": + time.sleep(2) + else: + device.delete() + break + self.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_device.py b/test/test_device.py new file mode 100644 index 0000000..9e470be --- /dev/null +++ b/test/test_device.py @@ -0,0 +1,76 @@ +import os +import sys +import unittest +import time +import packet + +from datetime import datetime + + +class TestDevice(unittest.TestCase): + @classmethod + def setUpClass(self): + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + org_id = self.manager.list_organizations()[0].id + self.project = self.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-Device_{}".format(datetime.utcnow().timestamp()) + ) + + self.manager.enable_project_bgp_config( + project_id=self.project.id, + deployment_type="local", + asn=65000 + ) + + self.device = self.manager.create_device( + self.project.id, "devicetest", "baremetal_0", "ewr1", "centos_7" + ) + + while True: + if self.manager.get_device(self.device.id).state == "active": + break + time.sleep(2) + + def test_get_device(self): + device = self.manager.get_device(self.device.id) + self.assertEqual(device.hostname, self.device.hostname) + + def test_list_devices(self): + devices = self.manager.list_devices(self.project.id) + for device in devices: + if device.id is self.device.id: + break + self.assertRaises(TypeError) + + def test_update_device(self): + self.device.hostname = "newname" + self.device.update() + device = self.manager.get_device(self.device.id) + self.assertEqual(self.device.hostname, device.hostname) + + def test_create_bgp_session(self): + bgp_session = self.manager\ + .create_bgp_session(self.device.id, address_family="ipv4") + self.assertIsNotNone(bgp_session) + + def test_get_bgp_sessions(self): + data = self.manager.get_bgp_sessions(self.device.id) + self.assertIsNotNone(self, data) + + def test_get_device_events(self): + events = self.manager.list_device_events(self.device.id) + self.assertGreater(len(events), 0) + + def test_get_device_ips(self): + ips = self.device.ips() + self.assertTrue(len(ips) > 0) + + @classmethod + def tearDownClass(self): + self.device.delete() + self.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_email.py b/test/test_email.py new file mode 100644 index 0000000..3cfd6e0 --- /dev/null +++ b/test/test_email.py @@ -0,0 +1,32 @@ +import os +import sys +import unittest +import packet +import random + + +class TestEmail(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + + cls.email = cls.manager.add_email("john.doe{}@packet.com".format(random.randint(1, 1001))) + + def test_get_email(self): + email = self.manager.get_email(self.email.id) + self.assertEqual(email.address, self.email.address) + + def test_update_email(self): + self.email.address = "john.doe{}@packet.com".format(random.randint(1, 1001)) + self.email.update() + # email address cannot be updated? + # email = self.manager.get_email(self.email.id) + # self.assertEqual(email.address, self.email.address) + + @classmethod + def tearDownClass(cls): + cls.email.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_event.py b/test/test_event.py new file mode 100644 index 0000000..6647c47 --- /dev/null +++ b/test/test_event.py @@ -0,0 +1,39 @@ +import os +import sys +import unittest +import packet + +from datetime import datetime + + +class TestEvent(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + + cls.events = cls.manager.list_events() + + org_id = cls.manager.list_organizations()[0].id + cls.project = cls.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-Events_{}".format(datetime.utcnow().timestamp()) + ) + + def test_list_events(self): + self.assertTrue(len(self.events) > 0) + + def test_get_event(self): + event = self.manager.get_event(self.events[0].id) + self.assertEqual(event.id, self.events[0].id) + + def test_get_project_events(self): + events = self.manager.list_project_events(self.project.id) + self.assertGreaterEqual(len(events), 0) + + @classmethod + def tearDownClass(cls): + cls.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_ips.py b/test/test_ips.py new file mode 100644 index 0000000..0d43a6c --- /dev/null +++ b/test/test_ips.py @@ -0,0 +1,67 @@ +import os +import time +import unittest +import packet + +from datetime import datetime + + +class TestIps(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + + org_id = cls.manager.list_organizations()[0].id + cls.project = cls.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-IPs_{}".format(datetime.utcnow().timestamp()) + ) + + cls.ipaddresses = cls.manager\ + .reserve_ip_address(project_id=cls.project.id, + type="global_ipv4", + quantity=1, + facility="EWR1", + details="delete me", + tags=["deleteme"]) + + cls.device = cls.manager.create_device( + cls.project.id, "iptest", "baremetal_0", "ewr1", "centos_7" + ) + + while True: + if cls.manager.get_device(cls.device.id).state == "active": + break + time.sleep(2) + + cls.manager.reserve_ip_address(project_id=cls.project.id, + type="global_ipv4", + quantity=1, + facility="ewr1") + + def list_project_ips(self): + ips = self.manager.list_project_ips(self.project.id) + + self.assertIsNotNone(ips) + + def test_create_device_ip(self): + ip = None + params = { + "include": ["facility"] + } + ips = self.manager.list_project_ips(self.project.id, + params=params) + for i in ips: + if i.facility.code == "ewr1" \ + and i.address_family == 4: + ip = i + break + + ipaddress = self.manager.create_device_ip(self.device.id, + address=ip.address) + self.assertIsNotNone(ipaddress) + + @classmethod + def tearDownClass(cls): + cls.device.delete() + cls.project.delete() diff --git a/test/test_organization.py b/test/test_organization.py new file mode 100644 index 0000000..b62a940 --- /dev/null +++ b/test/test_organization.py @@ -0,0 +1,42 @@ +import os +import sys +import unittest +import packet + + +class TestOrganization(unittest.TestCase): + @classmethod + def setUpClass(self): + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + orgs = self.manager.list_organizations() + self.org_id = orgs[0].id + + def test_organization(self): + org = self.manager.get_organization(org_id=self.org_id) + self.assertEqual(self.org_id, org.id) + + def test_create_organization_project(self): + project = self.manager.create_organization_project( + org_id=self.org_id, + name="live-tests-project", + payment_method_id=None, + customdata={"tag": "delete me"} + ) + self.assertIsNotNone(project) + project.delete() + + def test_list_organization_projects(self): + projects = self.manager.list_organization_projects(org_id=self.org_id) + self.assertGreater(len(projects), 0) + + def test_list_organization_devices(self): + devices = self.manager.list_organization_devices(org_id=self.org_id) + self.assertGreaterEqual(len(devices), 0) + + @classmethod + def tearDownClass(self): + pass + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_packet.py b/test/test_packet.py index 4675a9d..37238a9 100644 --- a/test/test_packet.py +++ b/test/test_packet.py @@ -14,6 +14,7 @@ def setUp(self): def test_get_user(self): user = self.manager.get_user() + self.assertEqual(user.get("full_name"), "Aaron Welch") def test_list_facilities(self): @@ -32,10 +33,10 @@ def test_list_plans(self): def test_list_operating_systems(self): oss = self.manager.list_operating_systems() - for os in oss: - str(os) - repr(os) - self.assertIsInstance(os, packet.OperatingSystem) + for o in oss: + str(o) + repr(o) + self.assertIsInstance(o, packet.OperatingSystem) def test_list_projects(self): projects = self.manager.list_projects() @@ -72,6 +73,14 @@ def test_list_devices(self): repr(device) self.assertIsInstance(device, packet.Device) + # TODO figure out how to properly handle this test case + # def test_list_all_devices(self): + # devices = self.manager.list_all_devices("438659f0") + # for device in devices: + # str(device) + # repr(device) + # self.assertIsInstance(device, packet.Device) + def test_create_device(self): device = self.manager.create_device( "438659f0", "hostname", "baremetal_0", "ewr1", "ubuntu_14_04" @@ -124,12 +133,10 @@ def test_get_ssh_key(self): self.assertIsInstance(key, packet.SSHKey) def test_create_ssh_key(self): - public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDI4pIqzpb5g3992h+yr527VRcaB68KE4vPjWPPoiQws49KIs2NMcOzS9QE4\ -641uW1u5ML2HgQdfYKMF/YFGnI1Y6xV637DjhDyZYV9LasUH49npSSJjsBcsk9JGfUpNAOdcgpFzK8V90eiOrOC5YncxdwwG8pwjFI9nNVPCl4hYEu1iXdy\ -ysHvkFfS2fklsNjLWrzfafPlaen+qcBxygCA0sFdW/7er50aJeghdBHnE2WhIKLUkJxnKadznfAge7oEe+3LLAPfP+3yHyvp2+H0IzmVfYvAjnzliYetqQ8\ -pg5ZW2BiJzvqz5PebGS70y/ySCNW1qQmJURK/Wc1bt9en" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDI4pIqzpb5g3992h+yr527VRcaB68KE4vPjWPPoiQws49KIs2NMcOzS9QE4641uW1u5ML2HgQdfYKMF/YFGnI1Y6xV637DjhDyZYV9LasUH49npSSJjsBcsk9JGfUpNAOdcgpFzK8V90eiOrOC5YncxdwwG8pwjFI9nNVPCl4hYEu1iXdyysHvkFfS2fklsNjLWrzfafPlaen+qcBxygCA0sFdW/7er50aJeghdBHnE2WhIKLUkJxnKadznfAge7oEe+3LLAPfP+3yHyvp2+H0IzmVfYvAjnzliYetqQ8pg5ZW2BiJzvqz5PebGS70y/ySCNW1qQmJURK/Wc1bt9en" - key = self.manager.create_ssh_key(label="sshkey-name", public_key=public_key) + key = self.manager\ + .create_ssh_key(label="sshkey-name", public_key=public_key) self.assertIsInstance(key, packet.SSHKey) self.assertEqual(key.key, public_key) @@ -152,7 +159,13 @@ def test_list_volumes(self): def test_create_volume(self): volume = self.manager.create_volume( - "438659f0", "volume description", "storage_0", "100", "ewr1", 7, "1day" + "438659f0", + "volume description", + "storage_0", + "100", + "ewr1", + 7, + "1day" ) self.assertIsInstance(volume, packet.Volume) @@ -196,15 +209,25 @@ def test_volume_create_snapshot(self): def test_volume_create_clone(self): volume = self.manager.get_volume("f9a8a263") - volume.create_clone() + volume.clone() def test_capacity(self): self.manager.get_capacity() + def test_get_bgp_config(self): + bgp = self.manager.get_bgp_config("1234") + self.assertIsNotNone(bgp) + def test_validate_capacity(self): - capacity = self.manager.validate_capacity([("ewr1", "baremetal_0", 10)]) + capacity = self.manager\ + .validate_capacity([("ewr1", "baremetal_0", 10)]) self.assertTrue(capacity) + # IP Addresses + def test_list_device_ips(self): + ips = self.manager.list_device_ips("e123s") + self.assertIsNotNone(ips) + class PacketMockManager(packet.Manager): def call_api(self, method, type="GET", params=None): @@ -219,7 +242,8 @@ def call_api(self, method, type="GET", params=None): if type == "DELETE": mock(requests_mock.ANY) - return super(PacketMockManager, self).call_api(method, type, params) + return super(PacketMockManager, self)\ + .call_api(method, type, params) fixture = "%s_%s" % (type.lower(), method.lower()) fixture = fixture.replace("/", "_").split("?")[0] @@ -231,7 +255,8 @@ def call_api(self, method, type="GET", params=None): j = json.load(data_file) mock(requests_mock.ANY, headers=headers, json=j) - return super(PacketMockManager, self).call_api(method, type, params) + return super(PacketMockManager, self)\ + .call_api(method, type, params) if __name__ == "__main__": diff --git a/test/test_ports.py b/test/test_ports.py new file mode 100644 index 0000000..6cdcd45 --- /dev/null +++ b/test/test_ports.py @@ -0,0 +1,76 @@ +import os +import sys +import time +import unittest +import packet + +from datetime import datetime + + +class TestPorts(unittest.TestCase): + @classmethod + def setUpClass(self): + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + + org_id = self.manager.list_organizations()[0].id + self.project = self.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-Ports_{}".format(datetime.utcnow().timestamp()) + ) + + self.device = self.manager.create_device( + self.project.id, "networktestingdevice", "baremetal_2", "ewr1", "centos_7") + self.vlan = self.manager.create_vlan(self.project.id, "ewr1") + self.vlan2 = self.manager.create_vlan(self.project.id, "ewr1") + + while True: + if self.manager.get_device(self.device.id).state == "active": + break + time.sleep(2) + self.device_port_id = self.device['network_ports'][0]['id'] + self.device_eth0_port_id = self.device['network_ports'][1]['id'] + + def test01_convert_layer2(self): + self.manager.convert_layer_2(self.device_port_id, self.vlan.id) + + def test02_remove_port(self): + self.manager.remove_port(self.device_port_id, self.vlan.id) + + def test03_assign_port(self): + self.manager.assign_port(self.device_port_id, self.vlan.id) + + def test04_bond_port(self): + self.manager.bond_ports(self.device_port_id, False) + + def test05_disbond_port(self): + self.manager.disbond_ports(self.device_port_id, False) + + def test06_assign_native_vlan(self): + # must remove vlan from any previous association and attach more than one vlan to the eth0 port to be able to + # choose a native vlan + self.manager.remove_port(self.device_port_id, self.vlan.id) + self.manager.assign_port(self.device_eth0_port_id, self.vlan.id) + self.manager.assign_port(self.device_eth0_port_id, self.vlan2.id) + self.manager.assign_native_vlan(self.device_eth0_port_id, self.vlan.id) + + def test07_remove_native_vlan(self): + self.manager.remove_native_vlan(self.device_eth0_port_id) + + def test08_convert_layer3(self): + ipadresses = list({ + "address_family": 6, + "public": False, + }) + self.manager.convert_layer_3(self.device_port_id, ipadresses) + + @classmethod + def tearDownClass(self): + self.manager.remove_port(self.device_eth0_port_id, self.vlan.id) + self.device.delete() + self.vlan2.delete() + self.vlan.delete() + self.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_vlan.py b/test/test_vlan.py new file mode 100644 index 0000000..9852b4c --- /dev/null +++ b/test/test_vlan.py @@ -0,0 +1,62 @@ +import os +import sys +import time +import unittest +import packet + +from datetime import datetime + + +class TestVlan(unittest.TestCase): + @classmethod + def setUpClass(self): + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + + org_id = self.manager.list_organizations()[0].id + self.project = self.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-VLAN_{}".format(datetime.utcnow().timestamp()) + ) + + self.device = self.manager.create_device( + self.project.id, "vlantesting", "baremetal_2", "ewr1", "centos_7") + + self.vlan = self.manager.create_vlan(self.project.id, "ewr1") + self.vlan2 = self.manager.create_vlan(self.project.id, "ewr1") + while True: + if self.manager.get_device(self.device.id).state == "active": + break + time.sleep(2) + self.device_port_id = self.device['network_ports'][0]['id'] + self.device_eth0_port_id = self.device['network_ports'][1]['id'] + # must convert to layer 2 to work with vlans + self.manager.convert_layer_2(self.device_port_id, self.vlan.id) + + def test_list_vlan(self): + vlans = self.manager.list_vlans(self.project.id) + self.assertTrue(len(vlans) > 0) + + def test_get_vlan(self): + vlan = self.vlan.get() + self.assertEqual(vlan['id'], self.vlan.id) + + def test_assign_port(self): + self.manager.disbond_ports(self.device_eth0_port_id, False) + self.manager.remove_port(self.device_port_id, self.vlan.id) + self.manager.assign_port(self.device_eth0_port_id, self.vlan.id) + self.manager.assign_port(self.device_eth0_port_id, self.vlan2.id) + self.vlan.assign_native_vlan(self.device_eth0_port_id) + + def test_remove_port(self): + self.vlan.remove_native_vlan(self.device_eth0_port_id) + + @classmethod + def tearDownClass(self): + self.device.delete() + self.vlan.delete() + self.vlan2.delete() + self.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_volume.py b/test/test_volume.py new file mode 100644 index 0000000..5e37a2c --- /dev/null +++ b/test/test_volume.py @@ -0,0 +1,105 @@ +import os +import sys +import unittest +import time +import packet + +from datetime import datetime + + +class TestVolume(unittest.TestCase): + @classmethod + def setUpClass(self): + self.timestamp = '' + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + self.projectId = self.manager.list_projects()[0].id + + org_id = self.manager.list_organizations()[0].id + self.project = self.manager.create_organization_project( + org_id=org_id, + name="Int-Tests-Volume_{}".format(datetime.utcnow().timestamp()) + ) + + self.volume = self.manager.create_volume(self.project.id, + "volume description", + "storage_1", + "100", + "ewr1", + 7, + "1day") + + while True: + if self.manager.get_volume(self.volume.id).state == "active": + break + time.sleep(2) + + self.device = self.manager.create_device( + self.project.id, "devicevolumestest", "baremetal_0", "ewr1", "centos_7" + ) + + self.policy = self.volume.create_snapshot_policy("1day", 2) + self.clone = self.volume.clone() + + while True: + if self.manager.get_device(self.device.id).state == "active": + break + time.sleep(2) + + def test_get_volume(self): + volume = self.manager.get_volume(self.volume.id) + self.assertEqual(volume.description, self.volume.description) + + def test_list_volumes(self): + volumes = self.manager.list_volumes(self.project.id) + for volume in volumes: + if volume.id is self.volume.id: + break + self.assertRaises(TypeError) + + def test_update_volume(self): + self.volume.description = "newdescription" + self.volume.update() + volume = self.manager.get_volume(self.volume.id) + self.assertEqual(self.volume.description, volume.description) + + def test_attach_volume(self): + self.volume.attach(self.device.id) + while True: + if self.manager.get_device(self.device.id).state == "active": + break + time.sleep(1) + + def test_detach_volume(self): + self.volume.detach() + while True: + if self.manager.get_device(self.device.id).state == "active": + break + time.sleep(1) + + def test_create_snapshot(self): + self.volume.create_snapshot() + + def test_get_snapshots(self): + snapshots = self.manager.get_snapshots(self.volume.id) + self.assertIsNotNone(snapshots) + self.__class__.timestamp = snapshots[0].timestamp + + def test_update_snapshot_policy(self): + self.policy = self.policy.update_snapshot_policy("1month", 3) + assert self.policy.frequency == "1month" + assert self.policy.count == 3 + + def test_restore_volume(self): + self.volume.restore(self.__class__.timestamp) + + @classmethod + def tearDownClass(self): + self.policy.delete() + self.volume.delete() + self.device.delete() + self.clone.delete() + self.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/test/test_vpn.py b/test/test_vpn.py new file mode 100644 index 0000000..31f2338 --- /dev/null +++ b/test/test_vpn.py @@ -0,0 +1,25 @@ +from __future__ import print_function + +import os +import sys +import unittest +import packet + + +class TestVpn(unittest.TestCase): + @classmethod + def setUpClass(self): + self.manager = packet.Manager(auth_token=os.environ['PACKET_AUTH_TOKEN']) + self.manager.turn_on_vpn() + + # def test_get_vpn_config(self): + # config = self.manager.get_vpn_configuration("ewr1") + # print(config) + + @classmethod + def tearDownClass(self): + self.manager.turn_off_vpn() + + +if __name__ == "__main__": + sys.exit(unittest.main()) diff --git a/tox.ini b/tox.ini index e74f179..1ed443f 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,9 @@ envlist = py27,py34,py35,py36,py37 skip_missing_interpreters=True [testenv] +setenv = + PACKET_AUTH_TOKEN = {env:PACKET_AUTH_TOKEN:} + deps = pytest pytest-cov From a99a34c46459b82b0119c585f3600a484aecea1b Mon Sep 17 00:00:00 2001 From: Nurfet Becirevic Date: Thu, 15 Aug 2019 01:41:45 +0200 Subject: [PATCH 2/6] Fix IP address class, methods & tests --- packet/IPAddress.py | 17 +++++++++++------ packet/Manager.py | 17 +++++++---------- test/test_ips.py | 45 +++++++++++++++++++-------------------------- 3 files changed, 37 insertions(+), 42 deletions(-) diff --git a/packet/IPAddress.py b/packet/IPAddress.py index e0bb8e0..f6f08f8 100644 --- a/packet/IPAddress.py +++ b/packet/IPAddress.py @@ -5,8 +5,10 @@ class IPAddress: - def __init__(self, data): - self.ip = data["id"] + def __init__(self, data, manager): + self.manager = manager + + self.id = data["id"] self.address_family = data["address_family"] self.netmask = data["netmask"] self.created_at = data["created_at"] @@ -35,8 +37,11 @@ def __init__(self, data): if "gateway" in data: self.gateway = data["gateway"] - def __str__(self): - return "%s" % self.code + def delete(self): + return self.manager.call_api("ips/%s" % self.id, type="DELETE") + + def __str__(self): + return "%s" % self.code - def __repr__(self): - return "{}: {}".format(self.__class__.__name__, self.id) + def __repr__(self): + return "{}: {}".format(self.__class__.__name__, self.id) diff --git a/packet/Manager.py b/packet/Manager.py index a5fa093..94e66e5 100644 --- a/packet/Manager.py +++ b/packet/Manager.py @@ -302,13 +302,13 @@ def list_device_ips(self, device_id): data = self.call_api("devices/%s/ips" % device_id, type="GET") ips = list() for jsoned in data["ip_addresses"]: - ip = IPAddress(jsoned) + ip = IPAddress(jsoned, self) ips.append(ip) return ips def get_ip(self, ip_id): data = self.call_api("ips/%s" % ip_id) - return IPAddress(data) + return IPAddress(data, self) def delete_ip(self, ip_id): self.call_api("ips/%s" % ip_id, type="DELETE") @@ -319,7 +319,7 @@ def list_project_ips(self, project_id, params={}): params=params) ips = list() for jsoned in data["ip_addresses"]: - ip = IPAddress(jsoned) + ip = IPAddress(jsoned, self) ips.append(ip) return ips @@ -338,7 +338,7 @@ def create_device_ip(self, device_id, address, data = self.call_api("/devices/%s/ips" % device_id, params=params, type="POST") - return IPAddress(data) + return IPAddress(data, self) def reserve_ip_address(self, project_id, type, quantity, facility, details=None, comments=None, tags=list()): @@ -351,12 +351,9 @@ def reserve_ip_address(self, project_id, type, quantity, facility, "tags": tags } - data = self.call_api("/projects/%s/ips" % project_id, params=request) - ips = list() - for i in data["ip_addresses"]: - ip = IPAddress(i) - ips.append(ip) - return ips + data = self.call_api("/projects/%s/ips" % project_id, + params=request, type="POST") + return IPAddress(data, self) # Batches def create_batch(self, project_id, params): diff --git a/test/test_ips.py b/test/test_ips.py index 0d43a6c..8d1de5e 100644 --- a/test/test_ips.py +++ b/test/test_ips.py @@ -1,4 +1,5 @@ import os +import sys import time import unittest import packet @@ -14,14 +15,14 @@ def setUpClass(cls): org_id = cls.manager.list_organizations()[0].id cls.project = cls.manager.create_organization_project( org_id=org_id, - name="Int-Tests-IPs_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-IPs_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) - cls.ipaddresses = cls.manager\ + cls.ip_block = cls.manager \ .reserve_ip_address(project_id=cls.project.id, - type="global_ipv4", + type="public_ipv4", quantity=1, - facility="EWR1", + facility="ewr1", details="delete me", tags=["deleteme"]) @@ -34,34 +35,26 @@ def setUpClass(cls): break time.sleep(2) - cls.manager.reserve_ip_address(project_id=cls.project.id, - type="global_ipv4", - quantity=1, - facility="ewr1") + def test_reserve_ip_address(self): + self.assertEqual(32, self.ip_block.cidr) + self.assertEqual("delete me", self.ip_block.details) - def list_project_ips(self): + def test_list_project_ips(self): ips = self.manager.list_project_ips(self.project.id) - - self.assertIsNotNone(ips) + self.assertGreater(len(ips), 0) def test_create_device_ip(self): - ip = None - params = { - "include": ["facility"] - } - ips = self.manager.list_project_ips(self.project.id, - params=params) - for i in ips: - if i.facility.code == "ewr1" \ - and i.address_family == 4: - ip = i - break - - ipaddress = self.manager.create_device_ip(self.device.id, - address=ip.address) - self.assertIsNotNone(ipaddress) + ip = self.manager.create_device_ip(self.device.id, + address=self.ip_block.address) + self.assertIsNotNone(ip) + self.assertEqual(ip.address, self.ip_block.address) @classmethod def tearDownClass(cls): cls.device.delete() + cls.ip_block.delete() cls.project.delete() + + +if __name__ == "__main__": + sys.exit(unittest.main()) From 5f64f740238916514d6138a359032494d0c9af7e Mon Sep 17 00:00:00 2001 From: Nurfet Becirevic Date: Thu, 15 Aug 2019 02:06:46 +0200 Subject: [PATCH 3/6] Fix python 2 incompatibility --- test/test_batch.py | 2 +- test/test_device.py | 2 +- test/test_event.py | 2 +- test/test_ports.py | 2 +- test/test_vlan.py | 2 +- test/test_volume.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_batch.py b/test/test_batch.py index 9795393..87933bc 100644 --- a/test/test_batch.py +++ b/test/test_batch.py @@ -14,7 +14,7 @@ def setUpClass(self): org_id = self.manager.list_organizations()[0].id self.project = self.manager.create_organization_project( org_id=org_id, - name="Int-Tests-Batch_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-Batch_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) self.batches = list() diff --git a/test/test_device.py b/test/test_device.py index 9e470be..ea53fcc 100644 --- a/test/test_device.py +++ b/test/test_device.py @@ -14,7 +14,7 @@ def setUpClass(self): org_id = self.manager.list_organizations()[0].id self.project = self.manager.create_organization_project( org_id=org_id, - name="Int-Tests-Device_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-Device_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) self.manager.enable_project_bgp_config( diff --git a/test/test_event.py b/test/test_event.py index 6647c47..edb7471 100644 --- a/test/test_event.py +++ b/test/test_event.py @@ -16,7 +16,7 @@ def setUpClass(cls): org_id = cls.manager.list_organizations()[0].id cls.project = cls.manager.create_organization_project( org_id=org_id, - name="Int-Tests-Events_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-Events_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) def test_list_events(self): diff --git a/test/test_ports.py b/test/test_ports.py index 6cdcd45..3a2ae28 100644 --- a/test/test_ports.py +++ b/test/test_ports.py @@ -15,7 +15,7 @@ def setUpClass(self): org_id = self.manager.list_organizations()[0].id self.project = self.manager.create_organization_project( org_id=org_id, - name="Int-Tests-Ports_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-Ports_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) self.device = self.manager.create_device( diff --git a/test/test_vlan.py b/test/test_vlan.py index 9852b4c..916749c 100644 --- a/test/test_vlan.py +++ b/test/test_vlan.py @@ -15,7 +15,7 @@ def setUpClass(self): org_id = self.manager.list_organizations()[0].id self.project = self.manager.create_organization_project( org_id=org_id, - name="Int-Tests-VLAN_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-VLAN_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) self.device = self.manager.create_device( diff --git a/test/test_volume.py b/test/test_volume.py index 5e37a2c..3fa765d 100644 --- a/test/test_volume.py +++ b/test/test_volume.py @@ -17,7 +17,7 @@ def setUpClass(self): org_id = self.manager.list_organizations()[0].id self.project = self.manager.create_organization_project( org_id=org_id, - name="Int-Tests-Volume_{}".format(datetime.utcnow().timestamp()) + name="Int-Tests-Volume_{}".format(datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]) ) self.volume = self.manager.create_volume(self.project.id, From 37f5916c554bcc4d61a1e2f006122e28058e4a9f Mon Sep 17 00:00:00 2001 From: Nurfet Becirevic Date: Sun, 18 Aug 2019 14:17:02 +0200 Subject: [PATCH 4/6] Using dict.get instead of if/else --- packet/Device.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/packet/Device.py b/packet/Device.py index 51bf8d3..cc53bca 100644 --- a/packet/Device.py +++ b/packet/Device.py @@ -22,20 +22,11 @@ def __init__(self, data, manager): self.bonding_mode = data["bonding_mode"] self.created_at = data["created_at"] self.updated_at = data["updated_at"] - if "ipxe_script_url" in data: - self.ipxe_script_url = data["ipxe_script_url"] - else: - self.ipxe_script_url = None - if "always_pxe" in data: - self.always_pxe = data["always_pxe"] - else: - self.always_pxe = False + self.ipxe_script_url = data.get("ipxe_script_url", None) + self.always_pxe = data.get("always_pxe", False) if "storage" in data: self.storage = data["storage"] - if "customdata" in data: - self.customdata = data["customdata"] - else: - self.customdata = None + self.customdata = data.get("customdata", None) self.operating_system = data["operating_system"] self.facility = data["facility"] self.project = data["project"] @@ -55,10 +46,7 @@ def __init__(self, data, manager): if "network_ports" in data: self.network_ports = data["network_ports"] self.href = data["href"] - if "spot_instance" in data: - self.spot_instance = data["spot_instance"] - else: - self.spot_instance = False + self.spot_instance = data.get("spot_instance", False) if "root_password" in data: self.root_password = data["root_password"] From 062223f43d4335204a6a7eec764aa10d3bb052ea Mon Sep 17 00:00:00 2001 From: jasmingacic Date: Tue, 17 Sep 2019 20:46:39 +0200 Subject: [PATCH 5/6] Updated create device function with parameter hardware_reservation_id --- packet/Manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packet/Manager.py b/packet/Manager.py index 94e66e5..cd8c8e4 100644 --- a/packet/Manager.py +++ b/packet/Manager.py @@ -120,6 +120,7 @@ def create_device( termination_time=None, user_ssh_keys=[], userdata="", + hardware_reservation_id="" ): params = { @@ -138,6 +139,8 @@ def create_device( "userdata": userdata, } + if hardware_reservation_id !="": + params["hardware_reservation_id"] = hardware_reservation_id if ipxe_script_url != "": params["always_pxe"] = always_pxe params["ipxe_script_url"] = ipxe_script_url From f22e901f248c05948f59895bf1abe5682fb9c491 Mon Sep 17 00:00:00 2001 From: jasmingacic Date: Tue, 17 Sep 2019 20:50:15 +0200 Subject: [PATCH 6/6] Updated create device function with parameter hardware_reservation_id --- packet/Manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packet/Manager.py b/packet/Manager.py index 94e66e5..ae05ec5 100644 --- a/packet/Manager.py +++ b/packet/Manager.py @@ -120,6 +120,7 @@ def create_device( termination_time=None, user_ssh_keys=[], userdata="", + hardware_reservation_id="" ): params = { @@ -138,6 +139,8 @@ def create_device( "userdata": userdata, } + if hardware_reservation_id != "": + params["hardware_reservation_id"] = hardware_reservation_id if ipxe_script_url != "": params["always_pxe"] = always_pxe params["ipxe_script_url"] = ipxe_script_url