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.0.1", CIDR: "192.168.0.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) server.WaitUntilReady("~/.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(cmd, false) server.Release(1, 1024, 1) }() } else { break } } // destroy everything created provider.TearDown()
Index ¶
- Variables
- func AllEnv(providerName string) ([]string, error)
- func MaybeEnv(providerName string) ([]string, error)
- func RequiredEnv(providerName string) ([]string, error)
- type DeployConfig
- type Error
- type Flavor
- type Provider
- func (p *Provider) CheapestServerFlavor(cores, ramMB int, regex string) (*Flavor, error)
- func (p *Provider) CheapestServerFlavors(cores, ramMB int, regex string, sets [][]string) ([]*Flavor, error)
- func (p *Provider) CheckServer(serverID string) (working bool, err error)
- func (p *Provider) Deploy(config *DeployConfig) error
- func (p *Provider) DestroyServer(serverID string) error
- func (p *Provider) ErrIsNoHardware(err error) bool
- func (p *Provider) GetQuota() (*Quota, error)
- func (p *Provider) GetServerByName(name string) *Server
- func (p *Provider) GetServerFlavor(idOrName string) (*Flavor, error)
- func (p *Provider) HeadNode() *Server
- func (p *Provider) InCloud() bool
- func (p *Provider) LocalhostServer(os string, postCreationScript []byte, configFiles string, cidr ...string) (*Server, error)
- func (p *Provider) PrivateKey() string
- func (p *Provider) Servers() map[string]*Server
- func (p *Provider) Spawn(os string, osUser string, flavorID string, diskGB int, ttd time.Duration, ...) (*Server, error)
- func (p *Provider) TearDown() error
- type Quota
- type Resources
- type Server
- func (s *Server) Alive(checkSSH ...bool) bool
- func (s *Server) Allocate(cores float64, ramMB, diskGB int)
- func (s *Server) CloseSSHSession(session *ssh.Session, clientIndex int)
- func (s *Server) CopyOver(files string) error
- func (s *Server) CreateFile(content string, dest string) error
- func (s *Server) CreateSharedDisk() error
- func (s *Server) Destroy() error
- func (s *Server) Destroyed() bool
- func (s *Server) DownloadFile(source string, dest string) error
- func (s *Server) GetTimeZone() (*time.Location, error)
- func (s *Server) GoneBad(permanentProblem ...string)
- func (s *Server) HasSpaceFor(cores float64, ramMB, diskGB int) int
- func (s *Server) HomeDir() (string, error)
- func (s *Server) IsBad() bool
- func (s *Server) Matches(os string, script []byte, configFiles string, flavor *Flavor, sharedDisk bool) bool
- func (s *Server) MkDir(dir string) error
- func (s *Server) MountSharedDisk(nfsServerIP string) error
- func (s *Server) NotBad() bool
- func (s *Server) PermanentProblem() string
- func (s *Server) Release(cores float64, ramMB, diskGB int)
- func (s *Server) RunCmd(cmd string, background bool) (stdout, stderr string, err error)
- func (s *Server) SSHClient() (*ssh.Client, int, error)
- func (s *Server) SSHSession() (*ssh.Session, int, error)
- func (s *Server) UploadFile(source string, dest string) error
- func (s *Server) WaitUntilReady(files string, postCreationScript []byte) error
- type SpawnUsingQuotaCallback
Constants ¶
This section is empty.
Variables ¶
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" )
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
AllEnv returns the environment variables that RequiredEnv() and MaybeEnv() would return.
func MaybeEnv ¶ added in v0.11.0
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 ¶
RequiredEnv returns the environment variables that are needed by the given provider before New() will work for it. See New() for possible providerNames.
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.0.0:18, allowing for 16381 servers to be Spawn()d // later, with a maximum ip of 192.168.63.254. 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.0.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.
type Flavor ¶
Flavor describes a "flavor" of server, which is a certain (virtual) hardware configuration
type Provider ¶
type Provider struct { Name string sync.RWMutex log15.Logger // 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(name string, resourceName string, savePath string, logger ...log15.Logger) (*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 ¶
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(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, but in the order that is best overall. That is, all flavors matching regex are considered as in a normal CheapestServerFlavor() call, to get the first flavor. Then this is repeated, excluding flavors in the set from which the first flavor came. This repeats until a matching flavor can't be found, a found flavor is not in one of your sets, or there are as many output flavors as input sets.
func (*Provider) CheckServer ¶
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(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 ¶
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
ErrIsNoHardware return true if the given error suggests failure to spawn a server due to lack of hardware.
func (*Provider) GetQuota ¶
GetQuota returns details of the maximum resources the user can request, and the current resources used.
func (*Provider) GetServerByName ¶ added in v0.10.0
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
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
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
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 ¶
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) Servers ¶
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(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 ¶
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 { AdminPass string Disk int // GB of available disk space Flavor *Flavor ID string IP string // ip address that you could SSH to IsHeadNode bool Name string // ought to correspond to the hostname OS string // the name of the Operating System image Script []byte // the content of a start-up script run on the server ConfigFiles string // files that you will CopyOver() and require to be on this Server, in CopyOver() format TTD time.Duration // amount of idle time allowed before destruction UserName string // the username needed to log in to the server // 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 (*Server) Alive ¶
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.
func (*Server) Allocate ¶
Allocate records that the given resources have now been used up on this server.
func (*Server) CloseSSHSession ¶ added in v0.14.0
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
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 ¶
CreateFile creates a new file with the given content on the server.
func (*Server) CreateSharedDisk ¶ added in v0.13.0
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) Destroyed ¶
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
DownloadFile downloads a file from the server and stores it locally. The directory for your local file must already exist.
func (*Server) GetTimeZone ¶ added in v0.9.0
GetTimeZone gets the server's time zone as a fixed time.Location in the fake timezone 'SER'; you should only rely on the offset to convert times.
func (*Server) GoneBad ¶ added in v0.10.0
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 ¶
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
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
IsBad tells you if GoneBad() has been called (more recently than NotBad()).
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 ¶
MkDir creates a directory (and it's parents as necessary) on the server. Requires sudo.
func (*Server) MountSharedDisk ¶ added in v0.13.0
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
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
PermanentProblem tells you if GoneBad("problem message") has been called, returning that reason the server is not usable.
func (*Server) RunCmd ¶
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 ¶
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
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) UploadFile ¶
UploadFile uploads a local file to the given location on the server.
func (*Server) WaitUntilReady ¶ added in v0.7.0
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.
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).