Пример #1
0
class Type(Base):
    api_endpoint = "/linode/types/{id}"
    properties = {
        'disk': Property(filterable=True),
        'id': Property(identifier=True),
        'label': Property(filterable=True),
        'network_out': Property(filterable=True),
        'price': Property(),
        'addons': Property(),
        'memory': Property(filterable=True),
        'transfer': Property(filterable=True),
        'vcpus': Property(filterable=True),
        'gpus': Property(filterable=True),
        # type_class is populated from the 'class' attribute of the returned JSON
    }

    def _populate(self, json):
        """
        Allows changing the name "class" in JSON to "type_class" in python
        """
        super()._populate(json)

        if 'class' in json:
            setattr(self, 'type_class', json['class'])
        else:
            setattr(self, 'type_class', None)

    # allow filtering on this converted type
    type_class = FilterableAttribute('class')
Пример #2
0
class User(Base):
    api_endpoint = "/account/users/{id}"
    id_attribute = 'username'

    properties = {
        'email': Property(),
        'username': Property(identifier=True, mutable=True),
        'restricted': Property(mutable=True),
    }

    @property
    def grants(self):
        """
        Retrieves the grants for this user.  If the user is unrestricted, this
        will result in an ApiError.  This is smart, and will only fetch from the
        api once unless the object is invalidated.

        :returns: The grants for this user.
        :rtype: linode.objects.account.UserGrants
        """
        from linode_api4.objects.account import UserGrants  # pylint: disable-all
        if not hasattr(self, '_grants'):
            resp = self._client.get(
                UserGrants.api_endpoint.format(username=self.username))

            grants = UserGrants(self._client, self.username, resp)
            self._set('_grants', grants)

        return self._grants

    def invalidate(self):
        if hasattr(self, '_grants'):
            del self._grants
        Base.invalidate(self)
Пример #3
0
class Region(Base):
    api_endpoint = "/regions/{id}"
    properties = {
        'id': Property(identifier=True),
        'country': Property(filterable=True),
        'capabilities': Property(),
    }
Пример #4
0
class Payment(Base):
    api_endpoint = "/account/payments/{id}"

    properties = {
        "id": Property(identifier=True),
        "date": Property(is_datetime=True),
        "amount": Property(),
    }
Пример #5
0
class IPv6Pool(Base):
    api_endpoint = '/networking/ipv6/pools/{}'
    id_attribute = 'range'

    properties = {
        'range': Property(identifier=True),
        'region': Property(slug_relationship=Region, filterable=True),
    }
Пример #6
0
class LongviewSubscription(Base):
    api_endpoint = 'longview/subscriptions/{id}'
    properties = {
        "id": Property(identifier=True),
        "label": Property(),
        "clients_included": Property(),
        "price": Property()
    }
Пример #7
0
class WhitelistEntry(Base):
    api_endpoint = "/profile/whitelist/{id}"

    properties = {
        'id': Property(identifier=True),
        'address': Property(),
        'netmask': Property(),
        'note': Property(),
    }
Пример #8
0
class Invoice(Base):
    api_endpoint = "/account/invoices/{id}"

    properties = {
        "id": Property(identifier=True),
        "label": Property(),
        "date": Property(is_datetime=True),
        "total": Property(),
    }
Пример #9
0
class AccountSettings(Base):
    api_endpoint = "/account/settings"
    id_attribute = 'managed'  # this isn't actually used

    properties = {
        "network_helper": Property(mutable=True),
        "managed": Property(),
        "longview_subscription":
        Property(slug_relationship=LongviewSubscription)
    }
Пример #10
0
class NodeBalancerNode(DerivedBase):
    api_endpoint = '/nodebalancers/{nodebalancer_id}/configs/{config_id}/nodes/{id}'
    derived_url_path = 'nodes'
    parent_id_name = 'config_id'

    properties = {
        'id': Property(identifier=True),
        'config_id': Property(identifier=True),
        'nodebalancer_id': Property(identifier=True),
        "label": Property(mutable=True),
        "address": Property(mutable=True),
        "weight": Property(mutable=True),
        "mode": Property(mutable=True),
        "status": Property(),
        'tags': Property(mutable=True),
    }

    def __init__(self, client, id, parent_id, nodebalancer_id=None, json=None):
        """
        We need a special constructor here because this object's parent
        has a parent itself.
        """
        if not nodebalancer_id and not isinstance(parent_id, tuple):
            raise ValueError(
                'NodeBalancerNode must either be created with a nodebalancer_id or a tuple of '
                '(config_id, nodebalancer_id) for parent_id!')

        if isinstance(parent_id, tuple):
            nodebalancer_id = parent_id[1]
            parent_id = parent_id[0]

        DerivedBase.__init__(self, client, id, parent_id, json=json)

        self._set('nodebalancer_id', nodebalancer_id)
Пример #11
0
class IPAddress(Base):
    api_endpoint = '/networking/ips/{address}'
    id_attribute = 'address'

    properties = {
        "address": Property(identifier=True),
        "gateway": Property(),
        "subnet_mask": Property(),
        "prefix": Property(),
        "type": Property(),
        "public": Property(),
        "rdns": Property(mutable=True),
        "linode_id": Property(),
        "region": Property(slug_relationship=Region, filterable=True),
    }

    @property
    def linode(self):
        from .linode import Instance  # pylint: disable-all
        if not hasattr(self, '_linode'):
            self._set('_linode', Instance(self._client, self.linode_id))
        return self._linode

    def to(self, linode):
        """
        This is a helper method for ip-assign, and should not be used outside
        of that context.  It's used to cleanly build an IP Assign request with
        pretty python syntax.
        """
        from .linode import Instance  # pylint: disable-all
        if not isinstance(linode, Instance):
            raise ValueError("IP Address can only be assigned to a Linode!")
        return {"address": self.address, "linode_id": linode.id}
Пример #12
0
class SSHKey(Base):
    """
    An SSH Public Key uploaded to your profile for use in Linode Instance deployments.
    """
    api_endpoint = '/profile/sshkeys/{id}'

    properties = {
        "id": Property(identifier=True),
        "label": Property(mutable=True),
        "ssh_key": Property(),
        "created": Property(is_datetime=True),
    }
Пример #13
0
class ObjectStorageKeys(Base):
    """
    A keypair that allows third-party applications to access Linode Object Storage.
    """
    api_endpoint = '/object-storage/keys/{id}'

    properties = {
        'id': Property(identifier=True),
        'label': Property(mutable=True),
        'access_key': Property(),
        'secret_key': Property(),
    }
Пример #14
0
class ObjectStorageCluster(Base):
    """
    A cluster where Object Storage is available.
    """
    api_endpoint = '/object-storage/clusters/{id}'

    properties = {
        'id': Property(identifier=True),
        'region': Property(slug_relationship=Region),
        'status': Property(),
        'domain': Property(),
        'static_site_domain': Property(),
    }
Пример #15
0
class Kernel(Base):
    api_endpoint = "/linode/kernels/{id}"
    properties = {
        "created": Property(is_datetime=True),
        "deprecated": Property(filterable=True),
        "description": Property(),
        "id": Property(identifier=True),
        "kvm": Property(filterable=True),
        "label": Property(filterable=True),
        "updates": Property(),
        "version": Property(filterable=True),
        "architecture": Property(filterable=True),
        "xen": Property(filterable=True),
    }
Пример #16
0
class Type(Base):
    api_endpoint = "/linode/types/{id}"
    properties = {
        'disk': Property(filterable=True),
        'id': Property(identifier=True),
        'label': Property(filterable=True),
        'network_out': Property(filterable=True),
        'price': Property(),
        'addons': Property(),
        'memory': Property(filterable=True),
        'transfer': Property(filterable=True),
        'vcpus': Property(filterable=True),
    }
Пример #17
0
class InvoiceItem(DerivedBase):
    api_endpoint = '/account/invoices/{invoice_id}/items'
    derived_url_path = 'items'
    parent_id_name = 'invoice_id'
    id_attribute = 'label'  # this has to be something

    properties = {
        'invoice_id': Property(identifier=True),
        'unit_price': Property(),
        'label': Property(),
        'amount': Property(),
        'quantity': Property(),
        #'from_date': Property(is_datetime=True), this is populated below from the "from" attribute
        'to': Property(is_datetime=True),
        #'to_date': Property(is_datetime=True), this is populated below from the "to" attribute
        'type': Property(),
    }

    def _populate(self, json):
        """
        Allows population of "from_date" from the returned "from" attribute which
        is a reserved word in python.  Also populates "to_date" to be complete.
        """
        super()._populate(json)

        self.from_date = datetime.strptime(json['from'], DATE_FORMAT)
        self.to_date = datetime.strptime(json['to'], DATE_FORMAT)
Пример #18
0
class Tag(Base):
    api_endpoint = '/tags/{label}'
    id_attribute = 'label'

    properties = {
        'label': Property(identifier=True),
    }

    def _get_raw_objects(self):
        """
        Helper function to populate the first page of raw objects for this tag.
        This has the side effect of creating the ``_raw_objects`` attribute of
        this object.
        """
        if not hasattr(self, '_raw_objects'):
            result = self._client.get(type(self).api_endpoint, model=self)

            # I want to cache this to avoid making duplicate requests, but I don't
            # want it in the __init__
            self._raw_objects = result  # pylint: disable=attribute-defined-outside-init

    def _api_get(self):
        """
        Override the default behavior and just return myself if I exist - this
        is how the python library works, but calling a GET to this endpoint in
        the API returns a collection of objects with this tag.  See ``objects``
        below.
        """
        # do this to allow appropriate 404ing if this tag doesn't exist
        self._get_raw_objects()

        return self

    @property
    def objects(self):
        """
        Returns a list of objects that have been given this tag.  Right now this
        is only Linodes, but in the future this may expand to be more.
        """
        self._get_raw_objects()

        # we need to give the URL of this object to the PaginatedList
        page_url = type(self).api_endpoint.format(**vars(self))

        objects_data = [o['data'] for o in self._raw_objects['data']]
        processed_objects = {
            'page': self._raw_objects['page'],
            'pages': self._raw_objects['pages'],
            'results': self._raw_objects['results'],
            'data': objects_data,
        }

        # return a PaginatedList of objects
        return PaginatedList.make_paginated_list(processed_objects,
                                                 self._client,
                                                 Instance,
                                                 page_url=page_url)
Пример #19
0
class KubeVersion(Base):
    """
    A KubeVersion is a version of Kubernetes that can be deployed on LKE.
    """
    api_endpoint = "/lke/versions/{id}"

    properties = {
        "id": Property(identifier=True),
    }
Пример #20
0
class Disk(DerivedBase):
    api_endpoint = '/linode/instances/{linode_id}/disks/{id}'
    derived_url_path = 'disks'
    parent_id_name='linode_id'

    properties = {
        'id': Property(identifier=True),
        'created': Property(is_datetime=True),
        'label': Property(mutable=True, filterable=True),
        'size': Property(filterable=True),
        'status': Property(filterable=True, volatile=True),
        'filesystem': Property(),
        'updated': Property(is_datetime=True),
        'linode_id': Property(identifier=True),
    }


    def duplicate(self):
        result = self._client.post(Disk.api_endpoint, model=self, data={})

        if not 'id' in result:
            raise UnexpectedResponseError('Unexpected response duplicating disk!', json=result)

        d = Disk(self._client, result['id'], self.linode_id, result)
        return d


    def reset_root_password(self, root_password=None):
        rpass = root_password
        if not rpass:
            rpass = Instance.generate_root_password()

        params = {
            'password': rpass,
        }

        result = self._client.post(Disk.api_endpoint, model=self, data=params)

        if not 'id' in result:
            raise UnexpectedResponseError('Unexpected response duplicating disk!', json=result)

        self._populate(result)
        if not root_password:
            return True, rpass
        return True


    def resize(self, new_size):
        self._client.post('{}/resize'.format(self.api_endpoint), model=self, data={"size": new_size})
Пример #21
0
class AuthorizedApp(Base):
    api_endpoint = "/profile/apps/{id}"

    properties = {
        "id": Property(identifier=True),
        "scopes": Property(),
        "label": Property(),
        "created": Property(is_datetime=True),
        "expiry": Property(is_datetime=True),
        "thumbnail_url": Property(),
        "website": Property(),
    }
Пример #22
0
class LongviewClient(Base):

    api_endpoint = '/longview/clients/{id}'

    properties = {
        "id": Property(identifier=True),
        "created": Property(is_datetime=True),
        "updated": Property(is_datetime=True),
        "label": Property(mutable=True, filterable=True),
        "install_code": Property(),
        "apps": Property(),
        "api_key": Property(),
    }
Пример #23
0
class InvoiceItem(DerivedBase):
    api_endpoint = '/account/invoices/{invoice_id}/items'
    derived_url_path = 'items'
    parent_id_name = 'invoice_id'

    # TODO - this object doesn't have its own ID .. this might need
    # special handling
    properties = {
        'invoice_id': Property(identifier=True),
        'unit_price': Property(),
        'label': Property(),
        'amount': Property(),
        'quantity': Property(),
        'from': Property(is_datetime=True),
        'to': Property(is_datetime=True),
        'type': Property(),
    }
Пример #24
0
class Tag(Base):
    api_endpoint = '/tags/{label}'
    id_attribute = 'label'

    properties = {
        'label': Property(identifier=True),
    }

    def _get_raw_objects(self):
        """
        Helper function to populate the first page of raw objects for this tag.
        This has the side effect of creating the ``_raw_objects`` attribute of
        this object.
        """
        if not hasattr(self, '_raw_objects'):
            result = self._client.get(type(self).api_endpoint, model=self)

            # I want to cache this to avoid making duplicate requests, but I don't
            # want it in the __init__
            self._raw_objects = result  # pylint: disable=attribute-defined-outside-init

        return self._raw_objects

    def _api_get(self):
        """
        Override the default behavior and just return myself if I exist - this
        is how the python library works, but calling a GET to this endpoint in
        the API returns a collection of objects with this tag.  See ``objects``
        below.
        """
        # do this to allow appropriate 404ing if this tag doesn't exist
        self._get_raw_objects()

        return self

    @property
    def objects(self):
        """
        Returns a list of objects with this Tag.  This list may contain any
        taggable object type.
        """
        data = self._get_raw_objects()

        return PaginatedList.make_paginated_list(
            data,
            self._client,
            TaggedObjectProxy,
            page_url=type(self).api_endpoint.format(**vars(self)))
Пример #25
0
class PersonalAccessToken(Base):
    api_endpoint = "/profile/tokens/{id}"

    properties = {
        "id": Property(identifier=True),
        "scopes": Property(),
        "label": Property(mutable=True),
        "created": Property(is_datetime=True),
        "token": Property(),
        "expiry": Property(is_datetime=True),
    }
Пример #26
0
class TicketReply(DerivedBase):
    api_endpoint = '/support/tickets/{ticket_id}/replies'
    derived_url_path = 'replies'
    parent_id_name='ticket_id'

    properties = {
        'id': Property(identifier=True),
        'ticket_id': Property(identifier=True),
        'description': Property(),
        'created': Property(is_datetime=True),
        'created_by': Property(),
        'from_linode': Property(),
    }
Пример #27
0
class LKENodePool(DerivedBase):
    """
    An LKE Node Pool describes a pool of Linode Instances that exist within an
    LKE Cluster.
    """
    api_endpoint = "/lke/clusters/{cluster_id}/pools/{id}"
    derived_url_path = 'pools'
    parent_id = "linode_id"

    properties = {
        "id": Property(identifier=True),
        "cluster_id": Property(identifier=True),
        "type": Property(slug_relationship=Type),
        "disks": Property(),
        "count": Property(mutable=True),
        "nodes":
        Property(volatile=True),  # this is formatted in _populate below
    }

    def _populate(self, json):
        """
        Parse Nodes into more useful LKENodePoolNode objects
        """
        if json is not None:
            new_nodes = [
                LKENodePoolNode(self._client, c) for c in json["nodes"]
            ]
            json["nodes"] = new_nodes

        super()._populate(json)

    def recycle(self):
        """
        Deleted and recreates all Linodes in this Node Pool in a rolling fashion.
        Completing this operation may take several minutes.  This operation will
        cause all local data on Linode Instances in this pool to be lost.
        """
        self._client.post("{}/recycle".format(LKENodePool.api_endpoint),
                          model=self)
        self.invalidate()
Пример #28
0
class StackScript(Base):
    api_endpoint = '/linode/stackscripts/{id}'
    properties = {
        "user_defined_fields": Property(),
        "label": Property(mutable=True, filterable=True),
        "rev_note": Property(mutable=True),
        "username": Property(filterable=True),
        "user_gravatar_id": Property(),
        "is_public": Property(mutable=True, filterable=True),
        "created": Property(is_datetime=True),
        "deployments_active": Property(),
        "script": Property(mutable=True),
        "images": Property(mutable=True,
                           filterable=True),  # TODO make slug_relationship
        "deployments_total": Property(),
        "description": Property(mutable=True, filterable=True),
        "updated": Property(is_datetime=True),
    }

    def _populate(self, json):
        """
        Override the populate method to map user_defined_fields to
        fancy values
        """
        Base._populate(self, json)

        mapped_udfs = []
        for udf in self.user_defined_fields:
            t = UserDefinedFieldType.text
            choices = None
            if hasattr(udf, 'oneof'):
                t = UserDefinedFieldType.select_one
                choices = udf.oneof.split(',')
            elif hasattr(udf, 'manyof'):
                t = UserDefinedFieldType.select_many
                choices = udf.manyof.split(',')

            mapped_udfs.append(
                UserDefinedField(
                    udf.name,
                    udf.label if hasattr(udf, 'label') else None,
                    udf.example if hasattr(udf, 'example') else None,
                    t,
                    choices=choices))

        self._set('user_defined_fields', mapped_udfs)
        ndist = [Image(self._client, d) for d in self.images]
        self._set('images', ndist)

    def _serialize(self):
        dct = Base._serialize(self)
        dct['images'] = [d.id for d in self.images]
        return dct
Пример #29
0
class Disk(DerivedBase):
    api_endpoint = '/linode/instances/{linode_id}/disks/{id}'
    derived_url_path = 'disks'
    parent_id_name = 'linode_id'

    properties = {
        'id': Property(identifier=True),
        'created': Property(is_datetime=True),
        'label': Property(mutable=True, filterable=True),
        'size': Property(filterable=True),
        'status': Property(filterable=True, volatile=True),
        'filesystem': Property(),
        'updated': Property(is_datetime=True),
        'linode_id': Property(identifier=True),
    }

    def duplicate(self):
        result = self._client.post(Disk.api_endpoint, model=self, data={})

        if not 'id' in result:
            raise UnexpectedResponseError(
                'Unexpected response duplicating disk!', json=result)

        d = Disk(self._client, result['id'], self.linode_id, result)
        return d

    def reset_root_password(self, root_password=None):
        rpass = root_password
        if not rpass:
            rpass = Instance.generate_root_password()

        params = {
            'password': rpass,
        }

        result = self._client.post(Disk.api_endpoint, model=self, data=params)

        if not 'id' in result:
            raise UnexpectedResponseError(
                'Unexpected response duplicating disk!', json=result)

        self._populate(result)
        if not root_password:
            return True, rpass
        return True

    def resize(self, new_size):
        """
        Resizes this disk.  The Linode Instance this disk belongs to must have
        sufficient space available to accommodate the new size, and must be
        offline.

        **NOTE** If resizing a disk down, the filesystem on the disk must still
        fit on the new disk size.  You may need to resize the filesystem on the
        disk first before performing this action.

        :param new_size: The intended new size of the disk, in MB
        :type new_size: int

        :returns: True if the resize was initiated successfully.
        :rtype: bool
        """
        self._client.post('{}/resize'.format(Disk.api_endpoint),
                          model=self,
                          data={"size": new_size})

        return True
Пример #30
0
class Instance(Base):
    api_endpoint = '/linode/instances/{id}'
    properties = {
        'id': Property(identifier=True, filterable=True),
        'label': Property(mutable=True, filterable=True),
        'group': Property(mutable=True, filterable=True),
        'status': Property(volatile=True),
        'created': Property(is_datetime=True),
        'updated': Property(volatile=True, is_datetime=True),
        'region': Property(slug_relationship=Region, filterable=True),
        'alerts': Property(mutable=True),
        'image': Property(slug_relationship=Image, filterable=True),
        'disks': Property(derived_class=Disk),
        'configs': Property(derived_class=Config),
        'type': Property(slug_relationship=Type),
        'backups': Property(),
        'ipv4': Property(),
        'ipv6': Property(),
        'hypervisor': Property(),
        'specs': Property(),
        'tags': Property(mutable=True),
    }

    @property
    def ips(self):
        """
        The ips related collection is not normalized like the others, so we have to
        make an ad-hoc object to return for its response
        """
        if not hasattr(self, '_ips'):
            result = self._client.get("{}/ips".format(Instance.api_endpoint),
                                      model=self)

            if not "ipv4" in result:
                raise UnexpectedResponseError(
                    'Unexpected response loading IPs', json=result)

            v4pub = []
            for c in result['ipv4']['public']:
                i = IPAddress(self._client, c['address'], c)
                v4pub.append(i)

            v4pri = []
            for c in result['ipv4']['private']:
                i = IPAddress(self._client, c['address'], c)
                v4pri.append(i)

            shared_ips = []
            for c in result['ipv4']['shared']:
                i = IPAddress(self._client, c['address'], c)
                shared_ips.append(i)

            slaac = IPAddress(self._client, result['ipv6']['slaac']['address'],
                              result['ipv6']['slaac'])
            link_local = IPAddress(self._client,
                                   result['ipv6']['link_local']['address'],
                                   result['ipv6']['link_local'])

            pools = []
            for p in result['ipv6']['global']:
                pools.append(IPv6Pool(self._client, p['range']))

            ips = MappedObject(
                **{
                    "ipv4": {
                        "public": v4pub,
                        "private": v4pri,
                        "shared": shared_ips,
                    },
                    "ipv6": {
                        "slaac": slaac,
                        "link_local": link_local,
                        "pools": pools,
                    },
                })

            self._set('_ips', ips)

        return self._ips

    @property
    def available_backups(self):
        """
        The backups response contains what backups are available to be restored.
        """
        if not hasattr(self, '_avail_backups'):
            result = self._client.get("{}/backups".format(
                Instance.api_endpoint),
                                      model=self)

            if not 'automatic' in result:
                raise UnexpectedResponseError(
                    'Unexpected response loading available backups!',
                    json=result)

            automatic = []
            for a in result['automatic']:
                cur = Backup(self._client, a['id'], self.id, a)
                automatic.append(cur)

            snap = None
            if result['snapshot']['current']:
                snap = Backup(self._client,
                              result['snapshot']['current']['id'], self.id,
                              result['snapshot']['current'])

            psnap = None
            if result['snapshot']['in_progress']:
                psnap = Backup(self._client,
                               result['snapshot']['in_progress']['id'],
                               self.id, result['snapshot']['in_progress'])

            self._set(
                '_avail_backups',
                MappedObject(
                    **{
                        "automatic": automatic,
                        "snapshot": {
                            "current": snap,
                            "in_progress": psnap,
                        }
                    }))

        return self._avail_backups

    @property
    def transfer(self):
        """
        Get per-linode transfer
        """
        if not hasattr(self, '_transfer'):
            result = self._client.get("{}/transfer".format(
                Instance.api_endpoint),
                                      model=self)

            if not 'used' in result:
                raise UnexpectedResponseError(
                    'Unexpected response when getting Transfer Pool!')

            mapped = MappedObject(**result)

            setattr(self, '_transfer', mapped)

        return self._transfer

    def _populate(self, json):
        if json is not None:
            # fixes ipv4 and ipv6 attribute of json to make base._populate work
            if 'ipv4' in json and 'address' in json['ipv4']:
                json['ipv4']['id'] = json['ipv4']['address']
            if 'ipv6' in json and isinstance(json['ipv6'], list):
                for j in json['ipv6']:
                    j['id'] = j['range']

        Base._populate(self, json)

    def invalidate(self):
        """ Clear out cached properties """
        if hasattr(self, '_avail_backups'):
            del self._avail_backups
        if hasattr(self, '_ips'):
            del self._ips
        if hasattr(self, '_transfer'):
            del self._transfer

        Base.invalidate(self)

    def boot(self, config=None):
        resp = self._client.post(
            "{}/boot".format(Instance.api_endpoint),
            model=self,
            data={'config_id': config.id} if config else None)

        if 'error' in resp:
            return False
        return True

    def shutdown(self):
        resp = self._client.post("{}/shutdown".format(Instance.api_endpoint),
                                 model=self)

        if 'error' in resp:
            return False
        return True

    def reboot(self):
        resp = self._client.post("{}/reboot".format(Instance.api_endpoint),
                                 model=self)

        if 'error' in resp:
            return False
        return True

    def resize(self, new_type, **kwargs):
        new_type = new_type.id if issubclass(type(new_type),
                                             Base) else new_type

        params = {
            "type": new_type,
        }
        params.update(kwargs)

        resp = self._client.post("{}/resize".format(Instance.api_endpoint),
                                 model=self,
                                 data=params)

        if 'error' in resp:
            return False
        return True

    @staticmethod
    def generate_root_password():
        def _func(value):
            if sys.version_info[0] < 3:
                value = int(value.encode('hex'), 16)
            return value

        password = ''.join([
            PASSWORD_CHARS[_func(c) % len(PASSWORD_CHARS)]
            for c in urandom(randint(50, 110))
        ])

        # ensure the generated password is not too long
        if len(password) > 110:
            password = password[:110]

        return password

    # create derived objects
    def config_create(self,
                      kernel=None,
                      label=None,
                      devices=[],
                      disks=[],
                      volumes=[],
                      **kwargs):
        """
        Creates a Linode Config with the given attributes.

        :param kernel: The kernel to boot with.
        :param label: The config label
        :param disks: The list of disks, starting at sda, to map to this config.
        :param volumes: The volumes, starting after the last disk, to map to this
            config
        :param devices: A list of devices to assign to this config, in device
            index order.  Values must be of type Disk or Volume. If this is
            given, you may not include disks or volumes.
        :param **kwargs: Any other arguments accepted by the api.

        :returns: A new Linode Config
        """
        # needed here to avoid circular imports
        from .volume import Volume  # pylint: disable=import-outside-toplevel

        hypervisor_prefix = 'sd' if self.hypervisor == 'kvm' else 'xvd'
        device_names = [
            hypervisor_prefix + string.ascii_lowercase[i] for i in range(0, 8)
        ]
        device_map = {
            device_names[i]: None
            for i in range(0, len(device_names))
        }

        if devices and (disks or volumes):
            raise ValueError(
                'You may not call config_create with "devices" and '
                'either of "disks" or "volumes" specified!')

        if not devices:
            if not isinstance(disks, list):
                disks = [disks]
            if not isinstance(volumes, list):
                volumes = [volumes]

            devices = []

            for d in disks:
                if d is None:
                    devices.append(None)
                elif isinstance(d, Disk):
                    devices.append(d)
                else:
                    devices.append(Disk(self._client, int(d), self.id))

            for v in volumes:
                if v is None:
                    devices.append(None)
                elif isinstance(v, Volume):
                    devices.append(v)
                else:
                    devices.append(Volume(self._client, int(v)))

        if not devices:
            raise ValueError('Must include at least one disk or volume!')

        for i, d in enumerate(devices):
            if d is None:
                pass
            elif isinstance(d, Disk):
                device_map[device_names[i]] = {'disk_id': d.id}
            elif isinstance(d, Volume):
                device_map[device_names[i]] = {'volume_id': d.id}
            else:
                raise TypeError('Disk or Volume expected!')

        params = {
            'kernel':
            kernel.id if issubclass(type(kernel), Base) else kernel,
            'label':
            label if label else "{}_config_{}".format(self.label,
                                                      len(self.configs)),
            'devices':
            device_map,
        }
        params.update(kwargs)

        result = self._client.post("{}/configs".format(Instance.api_endpoint),
                                   model=self,
                                   data=params)
        self.invalidate()

        if not 'id' in result:
            raise UnexpectedResponseError(
                'Unexpected response creating config!', json=result)

        c = Config(self._client, result['id'], self.id, result)
        return c

    def disk_create(self,
                    size,
                    label=None,
                    filesystem=None,
                    read_only=False,
                    image=None,
                    root_pass=None,
                    authorized_keys=None,
                    stackscript=None,
                    **stackscript_args):

        gen_pass = None
        if image and not root_pass:
            gen_pass = Instance.generate_root_password()
            root_pass = gen_pass

        authorized_keys = load_and_validate_keys(authorized_keys)

        if image and not label:
            label = "My {} Disk".format(image.label)

        params = {
            'size':
            size,
            'label':
            label
            if label else "{}_disk_{}".format(self.label, len(self.disks)),
            'read_only':
            read_only,
            'filesystem':
            filesystem,
            'authorized_keys':
            authorized_keys,
        }

        if image:
            params.update({
                'image':
                image.id if issubclass(type(image), Base) else image,
                'root_pass':
                root_pass,
            })

        if stackscript:
            params['stackscript_id'] = stackscript.id
            if stackscript_args:
                params['stackscript_data'] = stackscript_args

        result = self._client.post("{}/disks".format(Instance.api_endpoint),
                                   model=self,
                                   data=params)
        self.invalidate()

        if not 'id' in result:
            raise UnexpectedResponseError('Unexpected response creating disk!',
                                          json=result)

        d = Disk(self._client, result['id'], self.id, result)

        if gen_pass:
            return d, gen_pass
        return d

    def enable_backups(self):
        """
        Enable Backups for this Instance.  When enabled, we will automatically
        backup your Instance's data so that it can be restored at a later date.
        For more information on Instance's Backups service and pricing, see our
        `Backups Page`_

        .. _Backups Page: https://www.linode.com/backups
        """
        self._client.post("{}/backups/enable".format(Instance.api_endpoint),
                          model=self)
        self.invalidate()
        return True

    def cancel_backups(self):
        """
        Cancels Backups for this Instance.  All existing Backups will be lost,
        including any snapshots that have been taken.  This cannot be undone,
        but Backups can be re-enabled at a later date.
        """
        self._client.post("{}/backups/cancel".format(Instance.api_endpoint),
                          model=self)
        self.invalidate()
        return True

    def snapshot(self, label=None):
        result = self._client.post("{}/backups".format(Instance.api_endpoint),
                                   model=self,
                                   data={"label": label})

        if not 'id' in result:
            raise UnexpectedResponseError(
                'Unexpected response taking snapshot!', json=result)

        # so the changes show up the next time they're accessed
        if hasattr(self, '_avail_backups'):
            del self._avail_backups

        b = Backup(self._client, result['id'], self.id, result)
        return b

    def ip_allocate(self, public=False):
        """
        Allocates a new :any:`IPAddress` for this Instance.  Additional public
        IPs require justification, and you may need to open a :any:`SupportTicket`
        before you can add one.  You may only have, at most, one private IP per
        Instance.

        :param public: If the new IP should be public or private.  Defaults to
                       private.
        :type public: bool

        :returns: The new IPAddress
        :rtype: IPAddress
        """
        result = self._client.post("{}/ips".format(Instance.api_endpoint),
                                   model=self,
                                   data={
                                       "type": "ipv4",
                                       "public": public,
                                   })

        if not 'address' in result:
            raise UnexpectedResponseError('Unexpected response allocating IP!',
                                          json=result)

        i = IPAddress(self._client, result['address'], result)
        return i

    def rebuild(self, image, root_pass=None, authorized_keys=None, **kwargs):
        """
        Rebuilding an Instance deletes all existing Disks and Configs and deploys
        a new :any:`Image` to it.  This can be used to reset an existing
        Instance or to install an Image on an empty Instance.

        :param image: The Image to deploy to this Instance
        :type image: str or Image
        :param root_pass: The root password for the newly rebuilt Instance.  If
                          omitted, a password will be generated and returned.
        :type root_pass: str
        :param authorized_keys: The ssh public keys to install in the linode's
                                /root/.ssh/authorized_keys file.  Each entry may
                                be a single key, or a path to a file containing
                                the key.
        :type authorized_keys: list or str

        :returns: The newly generated password, if one was not provided
                  (otherwise True)
        :rtype: str or bool
        """
        ret_pass = None
        if not root_pass:
            ret_pass = Instance.generate_root_password()
            root_pass = ret_pass

        authorized_keys = load_and_validate_keys(authorized_keys)

        params = {
            'image': image.id if issubclass(type(image), Base) else image,
            'root_pass': root_pass,
            'authorized_keys': authorized_keys,
        }
        params.update(kwargs)

        result = self._client.post('{}/rebuild'.format(Instance.api_endpoint),
                                   model=self,
                                   data=params)

        if not 'id' in result:
            raise UnexpectedResponseError(
                'Unexpected response issuing rebuild!', json=result)

        # update ourself with the newly-returned information
        self._populate(result)

        if not ret_pass:
            return True
        else:
            return ret_pass

    def rescue(self, *disks):
        if disks:
            disks = {
                x: {
                    'disk_id': y
                }
                for x, y in zip(('sda', 'sdb', 'sdc', 'sdd', 'sde', 'sdf',
                                 'sdg'), disks)
            }
        else:
            disks = None

        result = self._client.post('{}/rescue'.format(Instance.api_endpoint),
                                   model=self,
                                   data={"devices": disks})

        return result

    def kvmify(self):
        """
        Converts this linode to KVM from Xen
        """
        self._client.post('{}/kvmify'.format(Instance.api_endpoint),
                          model=self)

        return True

    def mutate(self):
        """
        Upgrades this Instance to the latest generation type
        """
        self._client.post('{}/mutate'.format(Instance.api_endpoint),
                          model=self)

        return True

    def initiate_migration(self):
        """
        Initiates a pending migration that is already scheduled for this Linode
        Instance
        """
        self._client.post('{}/migrate'.format(Instance.api_endpoint),
                          model=self)

    def clone(self,
              to_linode=None,
              region=None,
              service=None,
              configs=[],
              disks=[],
              label=None,
              group=None,
              with_backups=None):
        """ Clones this linode into a new linode or into a new linode in the given region """
        if to_linode and region:
            raise ValueError(
                'You may only specify one of "to_linode" and "region"')

        if region and not service:
            raise ValueError(
                'Specifying a region requires a "service" as well')

        if not isinstance(configs, list) and not isinstance(
                configs, PaginatedList):
            configs = [configs]
        if not isinstance(disks, list) and not isinstance(
                disks, PaginatedList):
            disks = [disks]

        cids = [c.id if issubclass(type(c), Base) else c for c in configs]
        dids = [d.id if issubclass(type(d), Base) else d for d in disks]

        params = {
            "linode_id":
            to_linode.id if issubclass(type(to_linode), Base) else to_linode,
            "region": region.id if issubclass(type(region), Base) else region,
            "type": service.id if issubclass(type(service), Base) else service,
            "configs": cids if cids else None,
            "disks": dids if dids else None,
            "label": label,
            "group": group,
            "with_backups": with_backups,
        }

        result = self._client.post('{}/clone'.format(Instance.api_endpoint),
                                   model=self,
                                   data=params)

        if not 'id' in result:
            raise UnexpectedResponseError(
                'Unexpected response cloning Instance!', json=result)

        l = Instance(self._client, result['id'], result)
        return l

    @property
    def stats(self):
        """
        Returns the JSON stats for this Instance
        """
        # TODO - this would be nicer if we formatted the stats
        return self._client.get('{}/stats'.format(Instance.api_endpoint),
                                model=self)

    def stats_for(self, dt):
        """
        Returns stats for the month containing the given datetime
        """
        # TODO - this would be nicer if we formatted the stats
        if not isinstance(dt, datetime):
            raise TypeError('stats_for requires a datetime object!')
        return self._client.get('{}/stats/{}'.format(Instance.api_endpoint,
                                                     dt.strftime('%Y/%m')),
                                model=self)