class CloudBlockStorageVolume(BaseResource):
    """
    This class represents a Block Storage volume.
    """
    def __init__(self, *args, **kwargs):
        super(CloudBlockStorageVolume, self).__init__(*args, **kwargs)
        try:
            self._nova_volumes = pyrax.cloudservers.volumes
        except AttributeError:
            # This will happen in unit testing, where the full pyrax
            # namespace is not exposed. In that situation, there is
            # no need for the reference anyway
            pass
        self._snapshot_manager = BaseManager(self.manager.api,
                resource_class=CloudBlockStorageSnapshot,
                response_key="snapshot", uri_base="snapshots")


    def attach_to_instance(self, instance, mountpoint):
        """
        Attaches this volume to the cloud server instance at the
        specified mountpoint. This requires a call to the cloud servers
        API; it cannot be done directly.
        """
        instance_id = _resolve_id(instance)
        resp = self._nova_volumes.create_server_volume(instance_id,
                self.id, mountpoint)
        # The response should be a volume reference to this volume.
        return resp.id == self.id


    def detach(self):
        """
        Detaches this volume from any device it may be attached to. If it
        is not attached, nothing happens.
        """
        attachments = self.attachments
        if not attachments:
            # Not attached; no error needed, just return
            return
        # A volume can only be attached to one device at a time, but for some
        # reason this is a list instead of a singular value
        att = attachments[0]
        instance_id = att["server_id"]
        attachment_id = att["id"]
        self._nova_volumes.delete_server_volume(instance_id, attachment_id)


    def delete(self, force=False):
        """
        Volumes cannot be deleted if either a) they are attached to a device, or
        b) they have any snapshots. This method overrides the base delete()
        method to both better handle these failures, and also to offer a 'force'
        option. When 'force' is True, the volume is detached, and any dependent
        snapshots are deleted before calling the volume's delete.
        """
        if force:
            self.detach()
            self.delete_all_snapshots()
        try:
            super(CloudBlockStorageVolume, self).delete()
        except exc.VolumeNotAvailable:
            # Notify the user? Record it somewhere?
            # For now, just re-raise
            raise


    def create_snapshot(self, name=None, description=None, force=False):
        """
        Creates a snapshot of this volume, with an optional name and
        description.

        Normally snapshots will not happen if the volume is attached. To
        override this default behavior, pass force=True.
        """
        name = name or ""
        description = description or ""
        # Note that passing in non-None values is required for the _create_body
        # method to distinguish between this and the request to create and
        # instance.
        try:
            snap = self._snapshot_manager.create(volume=self, name=name,
                    description=description, force=force)
        except exc.BadRequest as e:
            msg = str(e)
            if "Invalid volume: must be available" in msg:
                # The volume for the snapshot was attached.
                raise exc.VolumeNotAvailable("Cannot create a snapshot from an "
                        "attached volume. Detach the volume before trying again, "
                        "or pass 'force=True' to the create_snapshot() call.")
            else:
                # Some other error
                raise
        except exc.ClientException as e:
            if e.code == 409:
                if "Request conflicts with in-progress" in str(e):
                    raise exc.VolumeNotAvailable("The volume is current "
                            "creating a snapshot. You must wait until that "
                            "completes before attempting to create an "
                            "additional snapshot.")
                else:
                    raise
            else:
                raise
        return snap


    def list_snapshots(self):
        """
        Returns a list of all snapshots of this volume.
        """
        return [snap for snap in self._snapshot_manager.list()
                if snap.volume_id == self.id]


    def delete_all_snapshots(self):
        """
        Locates all snapshots of this volume and deletes them.
        """
        for snap in self.list_snapshots():
            snap.delete()


    def _get_name(self):
        return self.display_name

    def _set_name(self, val):
        self.display_name = val

    name = property(_get_name, _set_name, None,
            "Convenience for referencing the display_name.")

    def _get_description(self):
        return self.display_description

    def _set_description(self, val):
        self.display_description = val

    description = property(_get_description, _set_description, None,
            "Convenience for referencing the display_description.")
Exemple #2
0
class CloudBlockStorageVolume(BaseResource):
    """
    This class represents a Block Storage volume.
    """
    def __init__(self, *args, **kwargs):
        super(CloudBlockStorageVolume, self).__init__(*args, **kwargs)
        try:
            region = self.manager.api.region_name
            self._nova_volumes = pyrax.connect_to_cloudservers(region).volumes
        except AttributeError:
            # This will happen in unit testing, where the full pyrax
            # namespace is not exposed. In that situation, there is
            # no need for the reference anyway
            pass
        self._snapshot_manager = BaseManager(self.manager.api,
                resource_class=CloudBlockStorageSnapshot,
                response_key="snapshot", uri_base="snapshots")


    def attach_to_instance(self, instance, mountpoint):
        """
        Attaches this volume to the cloud server instance at the
        specified mountpoint. This requires a call to the cloud servers
        API; it cannot be done directly.
        """
        instance_id = _resolve_id(instance)
        try:
            resp = self._nova_volumes.create_server_volume(instance_id,
                    self.id, mountpoint)
        except Exception as e:
            raise exc.VolumeAttachmentFailed("%s" % e)


    def detach(self):
        """
        Detaches this volume from any device it may be attached to. If it
        is not attached, nothing happens.
        """
        attachments = self.attachments
        if not attachments:
            # Not attached; no error needed, just return
            return
        # A volume can only be attached to one device at a time, but for some
        # reason this is a list instead of a singular value
        att = attachments[0]
        instance_id = att["server_id"]
        attachment_id = att["id"]
        try:
            self._nova_volumes.delete_server_volume(instance_id, attachment_id)
        except Exception as e:
            raise exc.VolumeDetachmentFailed("%s" % e)


    def delete(self, force=False):
        """
        Volumes cannot be deleted if either a) they are attached to a device, or
        b) they have any snapshots. This method overrides the base delete()
        method to both better handle these failures, and also to offer a 'force'
        option. When 'force' is True, the volume is detached, and any dependent
        snapshots are deleted before calling the volume's delete.
        """
        if force:
            self.detach()
            self.delete_all_snapshots()
        try:
            super(CloudBlockStorageVolume, self).delete()
        except exc.VolumeNotAvailable:
            # Notify the user? Record it somewhere?
            # For now, just re-raise
            raise


    def create_snapshot(self, name=None, description=None, force=False):
        """
        Creates a snapshot of this volume, with an optional name and
        description.

        Normally snapshots will not happen if the volume is attached. To
        override this default behavior, pass force=True.
        """
        name = name or ""
        description = description or ""
        # Note that passing in non-None values is required for the _create_body
        # method to distinguish between this and the request to create and
        # instance.
        try:
            snap = self._snapshot_manager.create(volume=self, name=name,
                    description=description, force=force)
        except exc.BadRequest as e:
            msg = str(e)
            if "Invalid volume: must be available" in msg:
                # The volume for the snapshot was attached.
                raise exc.VolumeNotAvailable("Cannot create a snapshot from an "
                        "attached volume. Detach the volume before trying again, "
                        "or pass 'force=True' to the create_snapshot() call.")
            else:
                # Some other error
                raise
        except exc.ClientException as e:
            if e.code == 409:
                if "Request conflicts with in-progress" in str(e):
                    raise exc.VolumeNotAvailable("The volume is current "
                            "creating a snapshot. You must wait until that "
                            "completes before attempting to create an "
                            "additional snapshot.")
                else:
                    raise
            else:
                raise
        return snap


    def list_snapshots(self):
        """
        Returns a list of all snapshots of this volume.
        """
        return [snap for snap in self._snapshot_manager.list()
                if snap.volume_id == self.id]


    def delete_all_snapshots(self):
        """
        Locates all snapshots of this volume and deletes them.
        """
        for snap in self.list_snapshots():
            snap.delete()


    def _get_name(self):
        return self.display_name

    def _set_name(self, val):
        self.display_name = val

    name = property(_get_name, _set_name, None,
            "Convenience for referencing the display_name.")

    def _get_description(self):
        return self.display_description

    def _set_description(self, val):
        self.display_description = val

    description = property(_get_description, _set_description, None,
            "Convenience for referencing the display_description.")
Exemple #3
0
class CloudDatabaseInstance(BaseResource):
    """
    This class represents a MySQL instance in the cloud. 
    """
    def __init__(self, *args, **kwargs):
        super(CloudDatabaseInstance, self).__init__(*args, **kwargs)
        self._database_manager = BaseManager(self.manager.api,
                resource_class=CloudDatabaseDatabase, response_key="database",
                uri_base="instances/%s/databases" % self.id)
        self._user_manager = BaseManager(self.manager.api,
                resource_class=CloudDatabaseUser, response_key="user",
                uri_base="instances/%s/users" % self.id)


    def list_databases(self):
        """Returns a list of the names of all databases for this instance."""
        return self._database_manager.list()


    def list_users(self):
        """Returns a list of the names of all users for this instance."""
        return self._user_manager.list()


    def get_database(self, name):
        """
        Finds the database in this instance with the specified name, and
        returns a CloudDatabaseDatabase object. If no match is found, a
        NoSuchDatabase exception is raised.
        """
        try:
            return [db for db in self.list_databases()
                    if db.name == name][0]
        except IndexError:
            raise exc.NoSuchDatabase("No database by the name '%s' exists." % name)


    def get_user(self, name):
        """
        Finds the user in this instance with the specified name, and
        returns a CloudDatabaseUser object. If no match is found, a
        NoSuchDatabaseUser exception is raised.
        """
        try:
            return [user for user in self.list_users()
                    if user.name == name][0]
        except IndexError:
            raise exc.NoSuchDatabaseUser("No user by the name '%s' exists." % name)


    def create_database(self, name, character_set=None, collate=None):
        """
        Creates a database with the specified name. If a database with
        that name already exists, a BadRequest (400) exception will
        be raised.
        """
        if character_set is None:
            character_set = "utf8"
        if collate is None:
            collate = "utf8_general_ci"
        # Note that passing in non-None values is required for the _create_body
        # method to distinguish between this and the request to create and instance.
        self._database_manager.create(name=name, character_set=character_set,
                collate=collate, return_none=True)
        # Since the API doesn't return the info for creating the database object, we
        # have to do it manually.
        return self._database_manager.find(name=name)


    def create_user(self, name, password, database_names):
        """
        Creates a user with the specified name and password, and gives that
        user access to the specified database(s).

        If a user with
        that name already exists, a BadRequest (400) exception will
        be raised.
        """
        if not isinstance(database_names, list):
            database_names = [database_names]
        # The API only accepts names, not DB objects
        database_names = [db if isinstance(db, basestring) else db.name
                for db in database_names]
        # Note that passing in non-None values is required for the create_body
        # method to distinguish between this and the request to create and instance.
        self._user_manager.create(name=name, password=password,
                database_names=database_names, return_none=True)
        # Since the API doesn't return the info for creating the user object, we
        # have to do it manually.
        return self._user_manager.find(name=name)


    def _get_name(self, name_or_obj):
        """
        For convenience, many methods accept either an object or the name
        of the object as a parameter, but need the name to send to the
        API. This method handles that conversion.
        """
        if isinstance(name_or_obj, basestring):
            return name_or_obj
        try:
            return name_or_obj.name
        except AttributeError:
            msg = "The object '%s' does not have a 'name' attribute." % name_or_obj
            raise exc.MissingName(msg)


    def delete_database(self, name_or_obj):
        """
        Deletes the specified database. If no database by that name
        exists, no exception will be raised; instead, nothing at all
        is done.
        """
        name = self._get_name(name_or_obj)
        self._database_manager.delete(name)


    def delete_user(self, name_or_obj):
        """
        Deletes the specified user. If no user by that name
        exists, no exception will be raised; instead, nothing at all
        is done.
        """
        name = self._get_name(name_or_obj)
        self._user_manager.delete(name)


    def enable_root_user(self):
        """
        Enables login from any host for the root user and provides
        the user with a generated root password.
        """
        uri = "/instances/%s/root" % self.id
        resp, body = self.manager.api.method_post(uri)
        return body["user"]["password"]


    def root_user_status(self):
        """
        Returns True or False, depending on whether the root user
        for this instance has been enabled.
        """
        uri = "/instances/%s/root" % self.id
        resp, body = self.manager.api.method_get(uri)
        return body["rootEnabled"]


    def restart(self):
        """Restarts this instance."""
        self.manager.action(self, "restart")


    def resize(self, flavor):
        """Set the size of this instance to a different flavor."""
        # We need the flavorRef, not the flavor or size.
        flavorRef = self.manager.api._get_flavor_ref(flavor)
        body = {"flavorRef": flavorRef}
        self.manager.action(self, "resize", body=body)


    def resize_volume(self, size):
        """Changes the size of the volume for this instance."""
        curr_size = self.volume.get("size")
        if size <= curr_size:
            raise exc.InvalidVolumeResize("The new volume size must be larger than the current volume size of '%s'." % curr_size)
        body = {"volume": {"size": size}}
        self.manager.action(self, "resize", body=body)


    def _get_flavor(self):
        try:
            ret = self._flavor
        except AttributeError:
            ret = self._flavor = CloudDatabaseFlavor(self.manager.api._flavor_manager, {})
        return ret

    def _set_flavor(self, flavor):
        if isinstance(flavor, dict):
            self._flavor = CloudDatabaseFlavor(self.manager.api._flavor_manager, flavor)
        else:
            # Must be an instance
            self._flavor = flavor

    flavor = property(_get_flavor, _set_flavor)
Exemple #4
0
class CloudDatabaseInstance(BaseResource):
    """
    This class represents a MySQL instance in the cloud.
    """
    def __init__(self, *args, **kwargs):
        super(CloudDatabaseInstance, self).__init__(*args, **kwargs)
        self._database_manager = BaseManager(
            self.manager.api,
            resource_class=CloudDatabaseDatabase,
            response_key="database",
            uri_base="instances/%s/databases" % self.id)
        self._user_manager = BaseManager(self.manager.api,
                                         resource_class=CloudDatabaseUser,
                                         response_key="user",
                                         uri_base="instances/%s/users" %
                                         self.id)
        # Remove the lazy load
        if not self.loaded:
            self.get()
            # Make the volume into an accessible object instead of a dict
            self.volume = CloudDatabaseVolume(self, self.volume)

    def list_databases(self):
        """Returns a list of the names of all databases for this instance."""
        return self._database_manager.list()

    def list_users(self):
        """Returns a list of the names of all users for this instance."""
        return self._user_manager.list()

    def get_database(self, name):
        """
        Finds the database in this instance with the specified name, and
        returns a CloudDatabaseDatabase object. If no match is found, a
        NoSuchDatabase exception is raised.
        """
        try:
            return [db for db in self.list_databases() if db.name == name][0]
        except IndexError:
            raise exc.NoSuchDatabase("No database by the name '%s' exists." %
                                     name)

    def get_user(self, name):
        """
        Finds the user in this instance with the specified name, and
        returns a CloudDatabaseUser object. If no match is found, a
        NoSuchDatabaseUser exception is raised.
        """
        try:
            return [user for user in self.list_users() if user.name == name][0]
        except IndexError:
            raise exc.NoSuchDatabaseUser("No user by the name '%s' exists." %
                                         name)

    def create_database(self, name, character_set=None, collate=None):
        """
        Creates a database with the specified name. If a database with
        that name already exists, a BadRequest (400) exception will
        be raised.
        """
        if character_set is None:
            character_set = "utf8"
        if collate is None:
            collate = "utf8_general_ci"
        # Note that passing in non-None values is required for the _create_body
        # method to distinguish between this and the request to create and
        # instance.
        self._database_manager.create(name=name,
                                      character_set=character_set,
                                      collate=collate,
                                      return_none=True)
        # Since the API doesn't return the info for creating the database
        # object, we have to do it manually.
        return self._database_manager.find(name=name)

    def create_user(self, name, password, database_names):
        """
        Creates a user with the specified name and password, and gives that
        user access to the specified database(s).

        If a user with
        that name already exists, a BadRequest (400) exception will
        be raised.
        """
        if not isinstance(database_names, list):
            database_names = [database_names]
        # The API only accepts names, not DB objects
        database_names = [
            db if isinstance(db, basestring) else db.name
            for db in database_names
        ]
        # Note that passing in non-None values is required for the create_body
        # method to distinguish between this and the request to create and
        # instance.
        self._user_manager.create(name=name,
                                  password=password,
                                  database_names=database_names,
                                  return_none=True)
        # Since the API doesn't return the info for creating the user object, we
        # have to do it manually.
        return self._user_manager.find(name=name)

    def _get_name(self, name_or_obj):
        """
        For convenience, many methods accept either an object or the name
        of the object as a parameter, but need the name to send to the
        API. This method handles that conversion.
        """
        if isinstance(name_or_obj, basestring):
            return name_or_obj
        try:
            return name_or_obj.name
        except AttributeError:
            msg = "The object '%s' does not have a 'name' attribute." % name_or_obj
            raise exc.MissingName(msg)

    def delete_database(self, name_or_obj):
        """
        Deletes the specified database. If no database by that name
        exists, no exception will be raised; instead, nothing at all
        is done.
        """
        name = self._get_name(name_or_obj)
        self._database_manager.delete(name)

    def delete_user(self, name_or_obj):
        """
        Deletes the specified user. If no user by that name
        exists, no exception will be raised; instead, nothing at all
        is done.
        """
        name = self._get_name(name_or_obj)
        self._user_manager.delete(name)

    def enable_root_user(self):
        """
        Enables login from any host for the root user and provides
        the user with a generated root password.
        """
        uri = "/instances/%s/root" % self.id
        resp, body = self.manager.api.method_post(uri)
        return body["user"]["password"]

    def root_user_status(self):
        """
        Returns True or False, depending on whether the root user
        for this instance has been enabled.
        """
        uri = "/instances/%s/root" % self.id
        resp, body = self.manager.api.method_get(uri)
        return body["rootEnabled"]

    def restart(self):
        """Restarts this instance."""
        self.manager.action(self, "restart")

    def resize(self, flavor):
        """Set the size of this instance to a different flavor."""
        # We need the flavorRef, not the flavor or size.
        flavorRef = self.manager.api._get_flavor_ref(flavor)
        body = {"flavorRef": flavorRef}
        self.manager.action(self, "resize", body=body)

    def resize_volume(self, size):
        """Changes the size of the volume for this instance."""
        curr_size = self.volume.size
        if size <= curr_size:
            raise exc.InvalidVolumeResize(
                "The new volume size must be larger "
                "than the current volume size of '%s'." % curr_size)
        body = {"volume": {"size": size}}
        self.manager.action(self, "resize", body=body)

    def _get_flavor(self):
        try:
            ret = self._flavor
        except AttributeError:
            ret = self._flavor = CloudDatabaseFlavor(
                self.manager.api._flavor_manager, {})
        return ret

    def _set_flavor(self, flavor):
        if isinstance(flavor, dict):
            self._flavor = CloudDatabaseFlavor(
                self.manager.api._flavor_manager, flavor)
        else:
            # Must be an instance
            self._flavor = flavor

    flavor = property(_get_flavor, _set_flavor)