cloud

package
v0.32.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 31, 2023 License: GPL-3.0 Imports: 51 Imported by: 0

Documentation

Overview

Package cloud provides functions to interact with cloud providers, used to create cloud resources so that you can spawn servers, then delete those resources when you're done.

Currently implemented providers are OpenStack, with AWS planned for the future. The implementation of each supported provider is in its own .go file.

It's a pseudo plug-in system in that it is designed so that you can easily add a go file that implements the methods of the provideri interface, to support a new cloud provider. On the other hand, there is no dynamic loading of these go files; they are all imported (they all belong to the cloud package), and the correct one used at run time. To "register" a new provideri implementation you must add a case for it to New() and RequiredEnv() and rebuild. The spawn() method must create a file at the path sentinelFilePath once the system has finalised its boot up and is fully ready to use. The system should be configured to allow user fuse mounts.

Please note that the methods in this package are NOT safe to be used by more than 1 process at a time.

    import "github.com/VertebrateResequencing/wr/cloud"

    // deploy
    provider, err := cloud.New("openstack", "wr-prod-username",
		"/home/username/.wr-production/created_cloud_resources")
    err = provider.Deploy(&cloud.DeployConfig{
        RequiredPorts:  []int{22},
        GatewayIP:      "192.168.64.1",
        CIDR:           "192.168.64.0/18",
        DNSNameServers: [...]string{"8.8.4.4", "8.8.8.8"},
    })

    // spawn a server
    flavor := provider.CheapestServerFlavor(1, 1024, "")
    server, err = provider.Spawn("Ubuntu Xenial", "ubuntu", flavor.ID, 20, 2 * time.Minute, true)
    ctx := context.Background()
    server.WaitUntilReady(ctx, "~/.s3cfg")

    // simplistic way of making the most of the server by running as many
    // commands as possible:
    for _, cmd := range myCmds {
        if server.HasSpaceFor(1, 1024, 1) > 0 {
            server.Allocate(1, 1024, 1)
            go func() {
                server.RunCmd(ctx, cmd, false)
                server.Release(1, 1024, 1)
            }()
        } else {
            break
        }
    }

    // destroy everything created
    provider.TearDown()

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBadProvider     = "unknown provider name"
	ErrMissingEnv      = "missing environment variables: "
	ErrBadResourceName = "your resource name prefix contains disallowed characters"
	ErrNoFlavor        = "no server flavor can meet your resource requirements"
	ErrBadFlavor       = "no server flavor with that id/name exists"
	ErrBadRegex        = "your flavor regular expression was not valid"
	ErrBadCIDR         = "no subnet matches your supplied CIDR"
	ErrNoTearDown      = "nothing to tear down; did you deploy with the current credentials?"
)

Err* constants are found in the returned Errors under err.Err, so you can cast and check if it's a certain type of error. ErrMissingEnv gets appended to with missing environment variable names, so check based on prefix.

Functions

func AllEnv added in v0.11.0

func AllEnv(providerName string) ([]string, error)

AllEnv returns the environment variables that RequiredEnv() and MaybeEnv() would return.

func MaybeEnv added in v0.11.0

func MaybeEnv(providerName string) ([]string, error)

MaybeEnv returns the environment variables that may be needed by the given provider. If one of these is actually needed but not provided, errors may appear from New() or later in subsequent attempted method usage.

func RequiredEnv

func RequiredEnv(providerName string) ([]string, error)

RequiredEnv returns the environment variables that are needed by the given provider before New() will work for it. See New() for possible providerNames.

func SFTPClient added in v0.21.0

func SFTPClient(conn *ssh.Client) (*sftp.Client, error)

SFTPClient is like sftp.NewClient(), but the underlying clientConn.conn.WriteCloser is mutex protected to avoid data races between closes due to errors and direct Close() calls on the *sftp.Client.

Types

type DeployConfig added in v0.5.0

type DeployConfig struct {
	// RequiredPorts is the slice of port numbers that your application needs to
	// be able to communicate to any servers you spawn (eg. [22] for ssh)
	// through. This will typically translate to the creation of security groups
	// that open up these ports. If your cloud network has all ports open and
	// does not allow the application of security groups to created servers,
	// then provide an empty slice.
	RequiredPorts []int

	// UseConfigDrive, if set to true (default false), will cause all newly
	// spawned servers to mount a configuration drive, which is typically needed
	// for a network without DHCP.
	UseConfigDrive bool

	// CIDR is used to either determine which existing network (that the current
	// cloud host is attached to) to spawn new servers on, or to define the
	// properties of the network and subnet if those need to be created. CIDR
	// defaults to 192.168.64.0/18, allowing for 16384 servers to be Spawn()d
	// later, with a maximum ip of 192.168.127.255.
	CIDR string

	// GatewayIP is used if a network and subnet needed to be created, and is
	// the gateway IP address of the created subnet. It defaults to
	// 192.168.64.1.
	GatewayIP string

	// DNSNameServers is a slice of DNS name server IPs. It defaults to
	// Google's: []string{"8.8.4.4", "8.8.8.8"}.
	DNSNameServers []string
}

DeployConfig are the configuration options that you supply to Deploy().

type Error

type Error struct {
	Provider string // the provider's Name
	Op       string // name of the method
	Err      string // one of our Err* vars
}

Error records an error and the operation and provider caused it.

func (Error) Error

func (e Error) Error() string

type Flavor

type Flavor struct {
	ID    string
	Name  string
	Cores int
	RAM   int // MB
	Disk  int // GB
}

Flavor describes a "flavor" of server, which is a certain (virtual) hardware configuration

func (*Flavor) HasSpaceFor added in v0.21.0

func (f *Flavor) HasSpaceFor(cores float64, ramMB, diskGB int) int

HasSpaceFor takes the cpu, ram and disk requirements of a command and tells you how many of those commands could run simultaneously on a server of our flavor. Returns 0 if not even 1 command could fit on a server with this flavor.

type Provider

type Provider struct {
	Name string

	sync.RWMutex
	// contains filtered or unexported fields
}

Provider gives you access to all of the methods you'll need to interact with a cloud provider.

func New

func New(ctx context.Context, name string, resourceName string, savePath string) (*Provider, error)

New creates a new Provider to interact with the given cloud provider. Possible names so far are "openstack" ("aws" is planned). You must provide a resource name that will be used to name any created cloud resources. You must also provide a file path prefix to save details of created resources to (the actual file created will be suffixed with your resourceName).

Note that the file could contain created private key details, so should be kept accessible only by you.

Providing a logger allows for debug messages to be logged somewhere, along with any "harmless" or unreturnable errors. If not supplied, we use a default logger that discards all log messages.

func (*Provider) CheapestServerFlavor

func (p *Provider) CheapestServerFlavor(ctx context.Context, cores, ramMB int, regex string) (*Flavor, error)

CheapestServerFlavor returns details of the smallest (cheapest) server "flavor" available that satisfies your minimum ram (MB) and CPU (core count) requirements, and that also matches the given regex (empty string for the regex means not limited by regex). Use the ID property of the return value for passing to Spawn(). If no flavor meets your requirements you will get an error matching ErrNoFlavor. You don't test for size of disk here, because during Spawn() you will request a certain amount of disk space, and if that is larger than the flavor's root disk a larger volume will be created automatically.

func (*Provider) CheapestServerFlavors added in v0.17.0

func (p *Provider) CheapestServerFlavors(ctx context.Context, cores, ramMB int,
	regex string, sets [][]string) ([]*Flavor, error)

CheapestServerFlavors is like CheapestServerFlavor(), taking the same first 3 arguments, but also a slice of slices that describe sets of flavors. For example, [][]string{{"f1","f2"},{"f3","f4"}}. Here, flavors f1 and f2 are in one set, and f3 and f4 are in another. The names are treated as regular expressions so you can describe multiple flavors in a set with a single entry.

You will get back the cheapest server flavor in each set, in the order of the sets you supply. The length of the returned slice will match length of sets. If, say, the 3rd set does not have a suitable server at all, then the 3rd element of the returned slice will be nil.

In the special case that sets is an empty slice, returns the result of CheapestServerFlavor() in a 1 element slice.

func (*Provider) CheckServer

func (p *Provider) CheckServer(ctx context.Context, serverID string) (working bool, err error)

CheckServer asks the provider if the status of the given server (id retrieved via Spawn() or Servers()) indicates it is working fine. (If it's not and was previously thought to be a spawned server with an external IP, then it will be removed from the results of Servers().)

func (*Provider) Deploy

func (p *Provider) Deploy(ctx context.Context, config *DeployConfig) error

Deploy triggers the creation of required cloud resources such as networks, ssh keys, security profiles and so on, such that you can subsequently Spawn() and ssh to your created server successfully.

If a resource we need already exists with the resourceName you supplied to New(), we assume it belongs to us and we don't create another (so it is safe to call Deploy multiple times with the same args to New() and Deploy(): you don't need to check if you have already deployed).

Deploy() saves the resources it created to disk, which are what TearDown() will delete when you call it. (They are saved to disk so that TearDown() can work if you call it in a different session to when you Deploy()ed, and so that PrivateKey() can work if you call it in a different session to the Deploy() call that actually created the ssh key.)

If servers already seem to exist with a name prefix matching resourceName, we assume that they are from a prior deployment that crashed and you are now recovering the sitution; these servers can be retrieved with GetServerByName() and Destroy()ed. Note, however, that they aren't fully useable since we don't know the username needed to ssh to them.

func (*Provider) DestroyServer

func (p *Provider) DestroyServer(ctx context.Context, serverID string) error

DestroyServer destroys a server given its id, that you would have gotten from the ID property of Spawn()'s return value.

func (*Provider) ErrIsNoHardware added in v0.17.0

func (p *Provider) ErrIsNoHardware(err error) bool

ErrIsNoHardware return true if the given error suggests failure to spawn a server due to lack of hardware.

func (*Provider) GetQuota

func (p *Provider) GetQuota(ctx context.Context) (*Quota, error)

GetQuota returns details of the maximum resources the user can request, and the current resources used.

func (*Provider) GetServerByName added in v0.10.0

func (p *Provider) GetServerByName(name string) *Server

GetServerByName returns the Server with the given hostname. Returns nil if we did not spawn a server with that hostname.

func (*Provider) GetServerFlavor added in v0.12.0

func (p *Provider) GetServerFlavor(ctx context.Context, idOrName string) (*Flavor, error)

GetServerFlavor returns the flavor with the given ID or name. If no flavor exactly matches you will get an error matching ErrBadFlavor.

func (*Provider) HeadNode added in v0.7.0

func (p *Provider) HeadNode() *Server

HeadNode returns the first server created under this deployment that had an external IP. Returns nil if no such server was recorded.

func (*Provider) InCloud added in v0.4.0

func (p *Provider) InCloud() bool

InCloud tells you if your process is currently running on a cloud server where the *Server related methods will all work correctly. (That is, if this returns true, you are on the same network as any server you Spawn().)

func (*Provider) LocalhostServer added in v0.11.0

func (p *Provider) LocalhostServer(os string, postCreationScript []byte, configFiles string, cidr ...string) (*Server, error)

LocalhostServer returns a Server object with details of the host we are currently running on. No cloud API calls are made to construct this.

func (*Provider) PrivateKey

func (p *Provider) PrivateKey() string

PrivateKey returns a PEM format string of the private key that was created by Deploy() (on its first invocation with the same arguments to New()).

func (*Provider) ServerIsKnown added in v0.23.1

func (p *Provider) ServerIsKnown(serverID string) (known bool, err error)

ServerIsKnown asks the provider if the given server (id retrieved via Spawn() or Servers()) is known about by this provider. If this returns false, it doesn't necessarily mean the server doesn't exist; it could mean you supplied the wrong credentials for the resources file you passed to New(). For that reason, the resources file is not updated if this returns false, and Servers() will continue to return a server with the given serverID.

func (*Provider) Servers

func (p *Provider) Servers() map[string]*Server

Servers returns a mapping of serverID => *Server for all servers that were Spawn()ed with an external IP (including those spawned in past sessions where the same arguments to New() were used). You should use s.Alive() before trying to use one of these servers. Do not alter the return value!

func (*Provider) Spawn

func (p *Provider) Spawn(ctx context.Context, os string, osUser string, flavorID string,
	diskGB int, ttd time.Duration, externalIP bool, usingQuotaCB ...SpawnUsingQuotaCallback) (*Server, error)

Spawn creates a new server using an OS image with a name or ID prefixed with the given os name or ID, with the given flavor ID (that you could get from CheapestServerFlavor().ID) and at least the given amount of disk space (creating a temporary volume of the required size if the flavor's root disk is too small).

If you supply a non-zero value for the ttd argument, then this amount of time after the last s.Release() call you make (or after the last cmd you started with s.RunCmd exits) that causes the server to be considered idle, the server will be destroyed.

If you need an external IP so that you can ssh to the server externally, supply true for externalIP.

You can supply an optinoal callback function that will be called as soon as the new server request has gone out (and so is using up your quota), but before the new server has powered up (which is when Spawn() will return the new server details).

Returns a *Server so you can s.Destroy it later, find out its ip address so you can ssh to it, and get its admin password in case you need to sudo on the server. You will need to know the username that you can log in with on your chosen OS image. If you call Spawn() while running on a cloud server, then the newly spawned server will be in the same network and security group as the current server. If you get an err, you will want to call server.Destroy() as this is not done for you.

NB: the server will likely not be ready to use yet, having not completed its boot up; call server.WaitUntilReady() before trying to use the server for anything.

func (*Provider) TearDown

func (p *Provider) TearDown(ctx context.Context) error

TearDown deletes all resources recorded during Deploy() or loaded from a previous session during New(). It also deletes any servers with names prefixed with the resourceName given to the initial New() call. If currently running on a cloud server, however, it will not delete anything needed by this server, including the resource file that contains the private key.

type Quota

type Quota struct {
	MaxRAM        int // total MBs allowed
	MaxCores      int // total CPU cores allowed
	MaxInstances  int // max number of instances allowed
	MaxVolume     int // max GBs of volume storage that can be allocated
	UsedRAM       int
	UsedCores     int
	UsedInstances int
	UsedVolume    int
}

Quota struct describes the limit on what resources you are allowed to use (0 values mean that resource is unlimited), and how much you have already used.

type Resources

type Resources struct {
	ResourceName string             // the resource name prefix that resources were created with
	Details      map[string]string  // whatever key values the provider needs to describe what it created
	PrivateKey   string             // PEM format string of the key user would need to ssh in to any created servers
	Servers      map[string]*Server // the serverID => *Server mapping of any servers Spawn()ed with an external ip
}

Resources struct contains provider-specific details of every resource that we created, in a format understood by TearDown(), so that we can delete those resources when they're no longer needed. There are also fields for important things the user needs to know.

type Server

type Server struct {
	Script        []byte // the content of a start-up script run on the server
	DestroyScript []byte // the content of a script to run on the server before it is destoyted

	AdminPass   string
	PrivateKey  string // PEM format string of a private key that can be used to SSH to the server
	ID          string
	IP          string // ip address that you could SSH to
	Name        string // ought to correspond to the hostname
	OS          string // the name of the Operating System image
	ConfigFiles string // files that you will CopyOver() and require to be on this Server, in CopyOver() format
	UserName    string // the username needed to log in to the server

	Flavor *Flavor
	Disk   int           // GB of available disk space
	TTD    time.Duration // amount of idle time allowed before destruction

	IsHeadNode bool
	SharedDisk bool // the server will mount /shared
	// contains filtered or unexported fields
}

Server provides details of the server that Spawn() created for you, and some methods that let you keep track of how you use that server.

func NewServer added in v0.24.0

func NewServer(username, ip, key string) *Server

NewServer returns a Server with the minimal details set needed to SSH to it and use the various SSH-requiring methods. You will need to manually set other properties for other functionality to work.

func (*Server) Alive

func (s *Server) Alive(ctx context.Context, checkSSH ...bool) bool

Alive tells you if a server is usable. It first does the same check as Destroyed() before calling out to the provider. Supplying an optional boolean will double check the server to make sure it can be ssh'd to. If the server doesn't exist, it will be removed from the provider's resources file.

func (*Server) Allocate

func (s *Server) Allocate(ctx context.Context, cores float64, ramMB, diskGB int) bool

Allocate considers the current usage (according to prior calls) and records the given resources have now been used up on this server, if there was enough space. Returns true if there was enough space and the allocation occurred.

func (*Server) BadDuration added in v0.19.0

func (s *Server) BadDuration() time.Duration

BadDuration tells you how long it has been since the last GoneBad() call (when there hasn't been a NotBad() call since). Returns 0 seconds if not actually bad right now.

func (*Server) CloseSSHSession added in v0.14.0

func (s *Server) CloseSSHSession(ctx context.Context, session *ssh.Session, clientIndex int)

CloseSSHSession is used to close a session opened with SSHSession(). If the client used to create the session (as indicated by the supplied index, also retrieved from SSHSession()) was marked as bad, it will now be marked as good, on the assumption there is now "space" for a new session.

func (*Server) CopyOver added in v0.8.0

func (s *Server) CopyOver(ctx context.Context, files string) error

CopyOver uploads the given local files to the corresponding locations on the server. files argument is a comma separated list of local file paths. Absolute paths are uploaded to the same absolute path on the server. Paths beginning with ~/ are uploaded from the local home directory to the server's home directory.

If local path and desired remote path are unrelated, the paths can be separated with a colon.

If a specified local path does not exist, it is silently ignored, allowing the specification of multiple possible config files when you might only have one. The mtimes of the files are retained.

NB: currently only works if the server supports the command 'pwd'.

func (*Server) CreateFile

func (s *Server) CreateFile(ctx context.Context, content string, dest string) error

CreateFile creates a new file with the given content on the server.

func (*Server) CreateSharedDisk added in v0.13.0

func (s *Server) CreateSharedDisk() error

CreateSharedDisk creates an NFS share at /shared, which must be empty or not exist. This does not work for remote Servers, so only call this on the return value of LocalhostServer(). Does nothing and returns nil if the share was already created. NB: this is currently hard-coded to only work on Ubuntu, and the ability to sudo is required! Also assumes you don't have any other shares configured, and no other process started the NFS server!

func (*Server) Destroy

func (s *Server) Destroy(ctx context.Context) error

Destroy destroys the server, first trying to run any script that was set with SetDestroyScript().

func (*Server) Destroyed

func (s *Server) Destroyed() bool

Destroyed tells you if a server was destroyed using Destroy() or the automatic destruction due to being idle. It is NOT the opposite of Alive(), since it does not check if the server is still usable.

func (*Server) DownloadFile added in v0.7.0

func (s *Server) DownloadFile(ctx context.Context, source string, dest string) error

DownloadFile downloads a file from the server and stores it locally. The directory for your local file must already exist.

func (*Server) GoneBad added in v0.10.0

func (s *Server) GoneBad(permanentProblem ...string)

GoneBad lets you mark a server as having something wrong with it, so you can avoid using it in the future, until the problems are confirmed. (At that point you'd either Destroy() it, or if this was a false alarm, call NotBad()).

The optional permanentProblem arg (some explanatory error message) makes it such that NotBad() will have no effect. For use when the server is Alive() but you just never want to re-use this server. The only reason you don't just Destroy() it is that you want to allow an end user to investigate the server manually.

func (*Server) HasSpaceFor

func (s *Server) HasSpaceFor(cores float64, ramMB, diskGB int) int

HasSpaceFor considers the current usage (according to prior Allocation calls) and tells you how many of a cmd needing the given resources can run on this server.

func (*Server) HomeDir added in v0.12.0

func (s *Server) HomeDir(ctx context.Context) (string, error)

HomeDir gets the absolute path to the server's home directory. Depends on 'pwd' command existing on the server.

func (*Server) IsBad added in v0.10.0

func (s *Server) IsBad() bool

IsBad tells you if GoneBad() has been called (more recently than NotBad()).

func (*Server) Known added in v0.23.1

func (s *Server) Known(ctx context.Context) bool

Known tells you if a server exists according to the provider. This can return false even if the server exists, because the credentials you used for the provider are different to the ones used to create this server. If the server isn't known about, the provider's resource file is NOT updated, because this indicates you're using the wrong resource file for these credentials.

func (*Server) Matches added in v0.12.0

func (s *Server) Matches(os string, script []byte, configFiles string, flavor *Flavor, sharedDisk bool) bool

Matches tells you if in principle a Server has the given os, script, config files, flavor and has a shared disk mounted. Useful before calling HasSpaceFor, since if you don't match these things you can't use the Server regardless of how empty it is. configFiles is in the CopyOver() format.

func (*Server) MkDir

func (s *Server) MkDir(ctx context.Context, dir string) error

MkDir creates a directory (and it's parents as necessary) on the server. Requires sudo.

func (*Server) MountSharedDisk added in v0.13.0

func (s *Server) MountSharedDisk(ctx context.Context, nfsServerIP string) error

MountSharedDisk can be used to mount a share from another Server (identified by its IP address) that you called CreateSharedDisk() on. The shared disk will be accessible at /shared. Does nothing and returns nil if the share was already mounted (or created on this Server). NB: currently hard-coded to use apt-get to install nfs-common on the server first, so probably only compatible with Ubuntu. Requires sudo.

func (*Server) NotBad added in v0.10.0

func (s *Server) NotBad() bool

NotBad lets you change your mind about a server you called GoneBad() on. (Unless GoneBad() was called with a permanentProblem, or the server has been destroyed).

func (*Server) PermanentProblem added in v0.10.0

func (s *Server) PermanentProblem() string

PermanentProblem tells you if GoneBad("problem message") has been called, returning that reason the server is not usable.

func (*Server) Release

func (s *Server) Release(ctx context.Context, cores float64, ramMB, diskGB int)

Release records that the given resources have now been freed.

func (*Server) RunCmd

func (s *Server) RunCmd(ctx context.Context, cmd string, background bool) (stdout, stderr string, err error)

RunCmd runs the given command on the server, optionally in the background. You get the command's STDOUT and STDERR as strings.

func (*Server) SSHClient

func (s *Server) SSHClient(ctx context.Context) (*ssh.Client, int, error)

SSHClient returns an ssh.Client object that could be used to ssh to the server. Requires that port 22 is accessible for SSH. The client returned will be one that hasn't failed to create a session yet; a new client will be created if necessary. You get back the client's index, so that if this client fails to create a session you can mark this client as bad.

func (*Server) SSHSession added in v0.10.0

func (s *Server) SSHSession(ctx context.Context) (*ssh.Session, int, error)

SSHSession returns an ssh.Session object that could be used to do things via ssh on the server. Will time out and return an error if the session can't be created within 5s. Also returns the index of the client this session came from, so that when you can call CloseSSHSession() when you're done with the returned session.

func (*Server) SetDestroyScript added in v0.28.0

func (s *Server) SetDestroyScript(preDestroyScript []byte)

SetDestroyScript will result in future Destroy() calls first running the given script over ssh, if possible.

func (*Server) UploadFile

func (s *Server) UploadFile(ctx context.Context, source string, dest string) error

UploadFile uploads a local file to the given location on the server.

func (*Server) Used added in v0.19.0

func (s *Server) Used() bool

Used tells you if this server has ever had Allocate() called on it.

func (*Server) WaitUntilReady added in v0.7.0

func (s *Server) WaitUntilReady(ctx context.Context, files string, postCreationScript []byte) error

WaitUntilReady waits for the server to become fully ready: the boot process will have completed and ssh will work. This is not part of provider.Spawn() because you may not want or be able to ssh to your server, and so that you can Spawn() another server while waiting for this one to become ready. If you get an err, you will want to call server.Destroy() as this is not done for you.

You supply a context so that you can cancel waiting if you no longer need this server. Be sure to Destroy() it after cancelling.

files is a string in the format taken by the CopyOver() method; if supplied non-blank it will CopyOver the specified files (after the server is ready, before any postCreationScript is run).

postCreationScript is the []byte content of a script that will be run on the server (as the user supplied to Spawn()) once it is ready, and it will complete before this function returns; empty slice means do nothing.

type SpawnUsingQuotaCallback added in v0.11.0

type SpawnUsingQuotaCallback func()

SpawnUsingQuotaCallback is the callback function you supply to Spawn() that will be called as soon as the request for the new server has been issued and is counted as using up quota (but before it has powered up).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL