This document describes the major changes in Ganeti 2.0 compared to the 1.2 version.
The 2.0 version will constitute a rewrite of the ‘core’ architecture, paving the way for additional features in future 2.x versions.
Contents
Ganeti 1.2 has many scalability issues and restrictions due to its roots as software for managing small and ‘static’ clusters.
Version 2.0 will attempt to remedy first the scalability issues and then the restrictions.
While Ganeti 1.2 is usable, it severely limits the flexibility of the cluster administration and imposes a very rigid model. It has the following main scalability issues:
It also has a number of artificial restrictions, due to historical design:
| [1] | Replace disks will release the lock, but this is an exception and not a recommended way to operate |
The 2.0 version is intended to address some of these problems, and create a more flexible code base for future developments.
Among these problems, the single-operation at a time restriction is biggest issue with the current version of Ganeti. It is such a big impediment in operating bigger clusters that many times one is tempted to remove the lock just to do a simple operation like start instance while an OS installation is running.
Ganeti 1.2 has a single global lock, which is used for all cluster operations. This has been painful at various times, for example:
This poses scalability problems: as clusters grow in node and instance size it’s a lot more likely that operations which one could conceive should run in parallel (for example because they happen on different nodes) are actually stalling each other while waiting for the global lock, without a real reason for that to happen.
One of the main causes of this global lock (beside the higher difficulty of ensuring data consistency in a more granular lock model) is the fact that currently there is no long-lived process in Ganeti that can coordinate multiple operations. Each command tries to acquire the so called cmd lock and when it succeeds, it takes complete ownership of the cluster configuration and state.
Other scalability problems are due the design of the DRBD device model, which assumed at its creation a low (one to four) number of instances per node, which is no longer true with today’s hardware.
Ganeti 1.2 (and previous versions) have a fixed two-disks, one-NIC per instance model. This is a purely artificial restrictions, but it touches multiple areas (configuration, import/export, command line) that it’s more fitted to a major release than a minor one.
The fact that each command is a separate process that reads the cluster state, executes the command, and saves the new state is also an issue on big clusters where the configuration data for the cluster begins to be non-trivial in size.
In order to solve the scalability problems, a rewrite of the core design of Ganeti is required. While the cluster operations themselves won’t change (e.g. start instance will do the same things, the way these operations are scheduled internally will change radically.
The new design will change the cluster architecture to:
This differs from the 1.2 architecture by the addition of the master daemon, which will be the only entity to talk to the node daemons.
The changes for 2.0 can be split into roughly three areas:
The main changes will be switching from a per-process model to a daemon based model, where the individual gnt-* commands will be clients that talk to this daemon (see Master daemon). This will allow us to get rid of the global cluster lock for most operations, having instead a per-object lock (see Granular locking). Also, the daemon will be able to queue jobs, and this will allow the individual clients to submit jobs without waiting for them to finish, and also see the result of old requests (see Job Queue).
Beside these major changes, another ‘core’ change but that will not be as visible to the users will be changing the model of object attribute storage, and separate that into name spaces (such that an Xen PVM instance will not have the Xen HVM parameters). This will allow future flexibility in defining additional parameters. For more details see Object parameters.
The various changes brought in by the master daemon model and the read-write RAPI will require changes to the cluster security; we move away from Twisted and use HTTP(s) for intra- and extra-cluster communications. For more details, see the security document in the doc/ directory.
In Ganeti 2.0, we will have the following entities:
The master-daemon related interaction paths are:
There are also some additional interaction paths for exceptional cases:
The protocol between the master daemon and the node daemons will be changed from (Ganeti 1.2) Twisted PB (perspective broker) to HTTP(S), using a simple PUT/GET of JSON-encoded messages. This is done due to difficulties in working with the Twisted framework and its protocols in a multithreaded environment, which we can overcome by using a simpler stack (see the caveats section).
The protocol between the CLI/RAPI and the master daemon will be a custom one (called LUXI): on a UNIX socket on the master node, with rights restricted by filesystem permissions, the CLI/RAPI will talk to the master daemon using JSON-encoded messages.
The operations supported over this internal protocol will be encoded via a python library that will expose a simple API for its users. Internally, the protocol will simply encode all objects in JSON format and decode them on the receiver side.
For more details about the RAPI daemon see Remote API changes, and for the node daemon see Node daemon changes.
As described above, the protocol for making requests or queries to the master daemon will be a UNIX-socket based simple RPC of JSON-encoded messages.
The choice of UNIX was in order to get rid of the need of authentication and authorisation inside Ganeti; for 2.0, the permissions on the Unix socket itself will determine the access rights.
We will have two main classes of operations over this API:
The cluster query functions are usually short-duration, and are the equivalent of the OP_QUERY_* opcodes in Ganeti 1.2 (and they are internally implemented still with these opcodes). The clients are guaranteed to receive the response in a reasonable time via a timeout.
The job-related functions will be:
For more details of the actual operation list, see the Job Queue.
Both requests and responses will consist of a JSON-encoded message followed by the ETX character (ASCII decimal 3), which is not a valid character in JSON messages and thus can serve as a message delimiter. The contents of the messages will be a dictionary with two fields:
| method: | the name of the method called |
|---|---|
| args: | the arguments to the method, as a list (no keyword arguments allowed) |
Responses will follow the same format, with the two fields being:
| success: | a boolean denoting the success of the operation |
|---|---|
| result: | the actual result, or error message in case of failure |
There are two special value for the result field:
Users of the API that don’t use the provided python library should take care of the above two cases.
The daemon will be based around a main I/O thread that will wait for new requests from the clients, and that does the setup/shutdown of the other thread (pools).
There will two other classes of threads in the daemon:
In Ganeti 1.x there is no protection against failing over the master to a node with stale configuration. In effect, the responsibility of correct failovers falls on the admin. This is true both for the new master and for when an old, offline master startup.
Since in 2.x we are extending the cluster state to cover the job queue and have a daemon that will execute by itself the job queue, we want to have more resilience for the master role.
The following algorithm will happen whenever a node is ready to transition to the master role, either at startup time or at node failover:
read the configuration file and parse the node list contained within
query all the nodes and make sure we obtain an agreement via a quorum of at least half plus one nodes for the following:
- we have the latest configuration and job list (as determined by the serial number on the configuration and highest job ID on the job queue)
- if we are not failing over (but just starting), the quorum agrees that we are the designated master
- if any of the above is false, we prevent the current operation (i.e. we don’t become the master)
at this point, the node transitions to the master role
for all the in-progress jobs, mark them as failed, with reason unknown or something similar (master failed, etc.)
Since due to exceptional conditions we could have a situation in which no node can become the master due to inconsistent data, we will have an override switch for the master daemon startup that will assume the current node has the right data and will replicate all the configuration files to the other nodes.
Note: the above algorithm is by no means an election algorithm; it is a confirmation of the master role currently held by a node.
The logging system will be switched completely to the standard python logging module; currently it’s logging-based, but exposes a different API, which is just overhead. As such, the code will be switched over to standard logging calls, and only the setup will be custom.
With this change, we will remove the separate debug/info/error logs, and instead have always one logfile per daemon model:
Since the watcher will only submit jobs to the master for startup of the instances, its log file will contain less information than before, mainly that it will start the instance, but not the results.
The only change to the node daemon is that, since we need better concurrency, we don’t process the inter-node RPC calls in the node daemon itself, but we fork and process each request in a separate child.
Since we don’t have many calls, and we only fork (not exec), the overhead should be minimal.
A discussed alternative is to keep the current individual processes touching the cluster configuration model. The reasons we have not chosen this approach is:
Another area of discussion is moving away from Twisted in this new implementation. While Twisted has its advantages, there are also many disadvantages to using it:
Another issue is with the Twisted API stability - during the Ganeti 1.x lifetime, we had to to implement many times workarounds to changes in the Twisted version, so that for example 1.2 is able to use both Twisted 2.x and 8.x.
In the end, since we already had an HTTP server library for the RAPI, we just reused that for inter-node communication.
We want to make sure that multiple operations can run in parallel on a Ganeti Cluster. In order for this to happen we need to make sure concurrently run operations don’t step on each other toes and break the cluster.
This design addresses how we are going to deal with locking so that:
Reaching the maximum possible parallelism is a Non-Goal. We have identified a set of operations that are currently bottlenecks and need to be parallelised and have worked on those. In the future it will be possible to address other needs, thus making the cluster more and more parallel one step at a time.
This section only talks about parallelising Ganeti level operations, aka Logical Units, and the locking needed for that. Any other synchronization lock needed internally by the code is outside its scope.
The proposed library has these features:
Locking will be valid only on the master node and will not be a distributed operation. Therefore, in case of master failure, the operations currently running will be aborted and the locks will be lost; it remains to the administrator to cleanup (if needed) the operation result (e.g. make sure an instance is either installed correctly or removed).
A corollary of this is that a master-failover operation with both masters alive needs to happen while no operations are running, and therefore no locks are held.
All the locks will be represented by objects (like lockings.SharedLock), and the individual locks for each object will be created at initialisation time, from the config file.
The API will have a way to grab one or more than one locks at the same time. Any attempt to grab a lock while already holding one in the wrong order will be checked for, and fail.
At the first stage we have decided to provide the following locks:
All the instance locks will need to be taken before the node locks, and the node locks before the config lock. Locks will need to be acquired at the same time for multiple instances and nodes, and internal ordering will be dealt within the locking library, which, for simplicity, will just use alphabetical order.
Each lock has the following three possible statuses:
In order to convert to a more granular approach transparently each time we split a lock into more we’ll create a “metalock”, which will depend on those sub-locks and live for the time necessary for all the code to convert (or forever, in some conditions). When a metalock exists all converted code must acquire it in shared mode, so it can run concurrently, but still be exclusive with old code, which acquires it exclusively.
In the beginning the only such lock will be what replaces the current “command” lock, and will acquire all the locks in the system, before proceeding. This lock will be called the “Big Ganeti Lock” because holding that one will avoid any other concurrent Ganeti operations.
We might also want to devise more metalocks (eg. all nodes, all nodes+config) in order to make it easier for some parts of the code to acquire what it needs without specifying it explicitly.
In the future things like the node locks could become metalocks, should we decide to split them into an even more fine grained approach, but this will probably be only after the first 2.0 version has been released.
When a new instance or a new node is created an associated lock must be added to the list. The relevant code will need to inform the locking library of such a change.
This needs to be compatible with every other lock in the system, especially metalocks that guarantee to grab sets of resources without specifying them explicitly. The implementation of this will be handled in the locking library itself.
When instances or nodes disappear from the cluster the relevant locks must be removed. This is easier than adding new elements, as the code which removes them must own them exclusively already, and thus deals with metalocks exactly as normal code acquiring those locks. Any operation queuing on a removed lock will fail after its removal.
For the first version the locking library will only export synchronous operations, which will block till the needed lock are held, and only fail if the request is impossible or somehow erroneous.
In the future we may want to implement different types of asynchronous operations such as:
These operations can be used to prioritize operations based on available locks, rather than making them just blindly queue for acquiring them. The inherent risk, though, is that any code using the first operation, or setting a timeout for the second one, is susceptible to starvation and thus may never be able to get the required locks and complete certain tasks. Considering this providing/using these operations should not be among our first priorities.
For the first version of this code we’ll convert each Logical Unit to acquire/release the locks it needs, so locking will be at the Logical Unit level. In the future we may want to split logical units in independent “tasklets” with their own locking requirements. A different design doc (or mini design doc) will cover the move from Logical Units to tasklets.
In general when acquiring locks we should use a code path equivalent to:
lock.acquire()
try:
...
# other code
finally:
lock.release()
This makes sure we release all locks, and avoid possible deadlocks. Of course extra care must be used not to leave, if possible locked structures in an unusable state. Note that with Python 2.5 a simpler syntax will be possible, but we want to keep compatibility with Python 2.4 so the new constructs should not be used.
In order to avoid this extra indentation and code changes everywhere in the Logical Units code, we decided to allow LUs to declare locks, and then execute their code with their locks acquired. In the new world LUs are called like this:
# user passed names are expanded to the internal lock/resource name,
# then known needed locks are declared
lu.ExpandNames()
... some locking/adding of locks may happen ...
# late declaration of locks for one level: this is useful because sometimes
# we can't know which resource we need before locking the previous level
lu.DeclareLocks() # for each level (cluster, instance, node)
... more locking/adding of locks can happen ...
# these functions are called with the proper locks held
lu.CheckPrereq()
lu.Exec()
... locks declared for removal are removed, all acquired locks released ...
The Processor and the LogicalUnit class will contain exact documentation on how locks are supposed to be declared.
This library will provide an easy upgrade path to bring all the code to granular locking without breaking everything, and it will also guarantee against a lot of common errors. Code switching from the old “lock everything” lock to the new system, though, needs to be carefully scrutinised to be sure it is really acquiring all the necessary locks, and none has been overlooked or forgotten.
The code can contain other locks outside of this library, to synchronise other threaded code (eg for the job queue) but in general these should be leaf locks or carefully structured non-leaf ones, to avoid deadlock race conditions.
Granular locking is not enough to speed up operations, we also need a queue to store these and to be able to process as many as possible in parallel.
A Ganeti job will consist of multiple OpCodes which are the basic element of operation in Ganeti 1.2 (and will remain as such). Most command-level commands are equivalent to one OpCode, or in some cases to a sequence of opcodes, all of the same type (e.g. evacuating a node will generate N opcodes of type replace disks).
| [2] | We need replication in order to maintain the consistency across all nodes in the system; the master node only differs in the fact that now it is running the master daemon, but it if fails and we do a master failover, the jobs are still visible on the new master (though marked as failed). |
Failures to replicate a job to other nodes will be only flagged as errors in the master daemon log if more than half of the nodes failed, otherwise we ignore the failure, and rely on the fact that the next update (for still running jobs) will retry the update. For finished jobs, it is less of a problem.
Future improvements will look into checking the consistency of the job list and jobs themselves at master daemon startup.
Jobs are stored in the filesystem as individual files, serialized using JSON (standard serialization mechanism in Ganeti).
The choice of storing each job in its own file was made because:
The other possible choices that were discussed and discounted were:
All file operations have to be done atomically by writing to a temporary file and subsequent renaming. Except for log messages, every change in a job is stored and replicated to other nodes.
/var/lib/ganeti/queue/
job-1 (JSON encoded job description and status)
[…]
job-37
job-38
job-39
lock (Queue managing process opens this file in exclusive mode)
serial (Last job ID used)
version (Queue format version)
Locking in the job queue is a complicated topic. It is called from more than one thread and must be thread-safe. For simplicity, a single lock is used for the whole job queue.
A more detailed description can be found in doc/locking.rst.
RPC calls available between Ganeti master and node daemons:
RPC between Ganeti clients and the Ganeti master daemon supports the following operations:
Each job and each opcode has, at any time, one of the following states:
If the master is aborted while a job is running, the job will be set to the Error status once the master started again.
Archived jobs are kept in a separate directory, /var/lib/ganeti/queue/archive/. This is done in order to speed up the queue handling: by default, the jobs in the archive are not touched by any functions. Only the current (unarchived) jobs are parsed, loaded, and verified (if implemented) by the master daemon.
The queue has to be completely empty for Ganeti updates with changes in the job queue structure. In order to allow this, there will be a way to prevent new jobs entering the queue.
Across all cluster configuration data, we have multiple classes of parameters:
The following definitions for instance parameters will be used below:
| hypervisor parameter: | |
|---|---|
a hypervisor parameter (or hypervisor specific parameter) is defined as a parameter that is interpreted by the hypervisor support code in Ganeti and usually is specific to a particular hypervisor (like the kernel path for PVM which makes no sense for HVM). |
|
| backend parameter: | |
a backend parameter is defined as an instance parameter that can be shared among a list of instances, and is either generic enough not to be tied to a given hypervisor or cannot influence at all the hypervisor behaviour. For example: memory, vcpus, auto_balance All these parameters will be encoded into constants.py with the prefix “BE_” and the whole list of parameters will exist in the set “BES_PARAMETERS” |
|
| proper parameter: | |
a parameter whose value is unique to the instance (e.g. the name of a LV, or the MAC of a NIC) |
As a general rule, for all kind of parameters, “None” (or in JSON-speak, “nil”) will no longer be a valid value for a parameter. As such, only non-default parameters will be saved as part of objects in the serialization step, reducing the size of the serialized format.
Cluster parameters remain as today, attributes at the top level of the Cluster object. In addition, two new attributes at this level will hold defaults for the instances:
Node-related parameters are very few, and we will continue using the same model for these as previously (attributes on the Node object).
There are three new node flags, described in a separate section “node flags” below.
As described before, the instance parameters are split in three: instance proper parameters, unique to each instance, instance hypervisor parameters and instance backend parameters.
The “hvparams” and “beparams” are kept in two dictionaries at instance level. Only non-default parameters are stored (but once customized, a parameter will be kept, even with the same value as the default one, until reset).
The names for hypervisor parameters in the instance.hvparams subtree should be choosen as generic as possible, especially if specific parameters could conceivably be useful for more than one hypervisor, e.g. instance.hvparams.vnc_console_port instead of using both instance.hvparams.hvm_vnc_console_port and instance.hvparams.kvm_vnc_console_port.
There are some special cases related to disks and NICs (for example): a disk has both Ganeti-related parameters (e.g. the name of the LV) and hypervisor-related parameters (how the disk is presented to/named in the instance). The former parameters remain as proper-instance parameters, while the latter value are migrated to the hvparams structure. In 2.0, we will have only globally-per-instance such hypervisor parameters, and not per-disk ones (e.g. all NICs will be exported as of the same type).
Starting from the 1.2 list of instance parameters, here is how they will be mapped to the three classes of parameters:
To support the new cluster parameter design, additional features will be required from the hypervisor support implementations in Ganeti.
The hypervisor support implementation API will be extended with the following features:
| PARAMETERS: | class-level attribute holding the list of valid parameters for this hypervisor |
|---|---|
| CheckParamSyntax(hvparams): | |
| checks that the given parameters are valid (as in the names are valid) for this hypervisor; usually just comparing hvparams.keys() and cls.PARAMETERS; this is a class method that can be called from within master code (i.e. cmdlib) and should be safe to do so | |
| ValidateParameters(hvparams): | |
| verifies the values of the provided parameters against this hypervisor; this is a method that will be called on the target node, from backend.py code, and as such can make node-specific checks (e.g. kernel_path checking) |
The application of defaults to an instance is done in the Cluster object, via two new methods as follows:
The FillHV/BE transformations will be used, for example, in the RpcRunner when sending an instance for activation/stop, and the sent instance hvparams/beparams will have the final value (noded code doesn’t know about defaults).
LU code will need to self-call the transformation, if needed.
The parameter changes will have impact on the OpCodes, especially on the following ones:
Additionally, we will need new OpCodes to modify the cluster-level defaults for the be/hv sets of parameters.
One problem that might appear is that our classification is not complete or not good enough, and we’ll need to change this model. As the last resort, we will need to rollback and keep 1.2 style.
Another problem is that classification of one parameter is unclear (e.g. network_port, is this BE or HV?); in this case we’ll take the risk of having to move parameters later between classes.
The only security issue that we foresee is if some new parameters will have sensitive value. If so, we will need to have a way to export the config data while purging the sensitive value.
E.g. for the drbd shared secrets, we could export these with the values replaced by an empty string.
Ganeti 2.0 adds three node flags that change the way nodes are handled within Ganeti and the related infrastructure (iallocator interaction, RAPI data export).
Ganeti 2.0 allows more scalability in operation by introducing parallelization. However, a new bottleneck is reached that is the synchronization and replication of cluster configuration to all nodes in the cluster.
This breaks scalability as the speed of the replication decreases roughly with the size of the nodes in the cluster. The goal of the master candidate flag is to change this O(n) into O(1) with respect to job and configuration data propagation.
Only nodes having this flag set (let’s call this set of nodes the candidate pool) will have jobs and configuration data replicated.
The cluster will have a new parameter (runtime changeable) called candidate_pool_size which represents the number of candidates the cluster tries to maintain (preferably automatically).
This will impact the cluster operations as follows:
The caveats of this change are:
Advantages:
The default value for the candidate pool size will be set to 10 but this can be changed at cluster creation and modified any time later.
Testing on simulated big clusters with sequential and parallel jobs show that this value (10) is a sweet-spot from performance and load point of view.
In order to support better the situation in which nodes are offline (e.g. for repair) without altering the cluster configuration, Ganeti needs to be told and needs to properly handle this state for nodes.
This will result in simpler procedures, and less mistakes, when the amount of node failures is high on an absolute scale (either due to high failure rate or simply big clusters).
Nodes having this attribute set will not be contacted for inter-node RPC calls, will not be master candidates, and will not be able to host instances as primaries.
Setting this attribute on a node:
This attribute will impact the cluster operations as follows:
The code is changed in the following ways:
Caveats:
One of the advantages of implementing this flag is that it will allow in the future automation tools to automatically put the node in repairs and recover from this state, and the code (should/will) handle this much better than just timing out. So, future possible improvements (for later versions):
Alternatives considered: The RPC call model in 2.0 is, by default, much nicer - errors are logged in the background, and job/opcode execution is clearer, so we could simply not introduce this. However, having this state will make both the codepaths clearer (offline vs. temporary failure) and the operational model (it’s not a node with errors, but an offline node).
Due to parallel execution of jobs in Ganeti 2.0, we could have the following situation:
In order to prevent this situation, and to be able to get nodes into proper offline status easily, a new drained flag was added to the nodes.
This flag (which actually means “is being, or was drained, and is expected to go offline”), will prevent allocations on the node, but otherwise all other operations (start/stop instance, query, etc.) are working without any restrictions.
While these flags are implemented as separate flags, they are mutually-exclusive and are acting together with the master node role as a single node status value. In other words, a flag is only in one of these roles at a given time. The lack of any of these flags denote a regular node.
The current node status is visible in the gnt-cluster verify output, and the individual flags can be examined via separate flags in the gnt-node list output.
These new flags will be exported in both the iallocator input message and via RAPI, see the respective man pages for the exact names.
The main feature-level changes will be:
The storage options available in Ganeti 1.x were introduced based on then-current software (first DRBD 0.7 then later DRBD 8) and the estimated usage patters. However, experience has later shown that some assumptions made initially are not true and that more flexibility is needed.
One main assumption made was that disk failures should be treated as ‘rare’ events, and that each of them needs to be manually handled in order to ensure data safety; however, both these assumptions are false:
Note that we still don’t have fully-automated disk recovery as a goal, but our goal is to reduce the manual work needed.
As such, we plan the following main changes:
Additionally, a number of smaller enhancements are also planned: - support variable number of disks - support read-only disks
Future enhancements in the 2.x series, which do not require base design changes, might include:
Currently, when trying to identify or activate a new DRBD (or MD) device, the code scans all in-use devices in order to see if we find one that looks similar to our parameters and is already in the desired state or not. Since this needs external commands to be run, it is very slow when more than a few devices are already present.
Therefore, we will change the discovery model from dynamic to static. When a new device is logically created (added to the configuration) a free minor number is computed from the list of devices that should exist on that node and assigned to that device.
At device activation, if the minor is already in use, we check if it has our parameters; if not so, we just destroy the device (if possible, otherwise we abort) and start it with our own parameters.
This means that we in effect take ownership of the minor space for that device type; if there’s a user-created DRBD minor, it will be automatically removed.
The change will have the effect of reducing the number of external commands run per device from a constant number times the index of the first free DRBD minor to just a constant number.
We need to remove these device types because of two issues. First, DRBD7 has bad failure modes in case of dual failures (both network and disk - it cannot propagate the error up the device stack and instead just panics. Second, due to the asymmetry between primary and secondary in MD+DRBD mode, we cannot do live failover (not even if we had MD+DRBD8).
Using files instead of logical volumes for instance storage would allow us to get rid of the hard requirement for volume groups for testing clusters and it would also allow usage of SAN storage to do live failover taking advantage of this storage solution.
Currently, the LV to PV allocation mechanism is a very simple one: at each new request for a logical volume, tell LVM to allocate the volume in order based on the amount of free space. This is good for simplicity and for keeping the usage equally spread over the available physical disks, however it introduces a problem that an instance could end up with its (currently) two drives on two physical disks, or (worse) that the data and metadata for a DRBD device end up on different drives.
This is bad because it causes unneeded replace-disks operations in case of a physical failure.
The solution is to batch allocations for an instance and make the LVM handling code try to allocate as close as possible all the storage of one instance. We will still allow the logical volumes to spill over to additional disks as needed.
Note that this clustered allocation can only be attempted at initial instance creation, or at change secondary node time. At add disk time, or at replacing individual disks, it’s not easy enough to compute the current disk map so we’ll not attempt the clustering.
DRBD8 has a new feature that allow authentication of the peer at connect time. We can use this to prevent connecting to the wrong peer more that securing the connection. Even though we never had issues with wrong connections, it would be good to implement this.
The complete failure of a physical disk is very tedious to troubleshoot, mainly because of the many failure modes and the many steps needed. We can safely automate some of the steps, more specifically the vgreduce --removemissing using the following method:
check if all nodes have consistent volume groups
if yes, and previous status was yes, do nothing
if yes, and previous status was no, save status and restart
if no, and previous status was no, do nothing
if more than one node is inconsistent, do nothing
With a modified disk activation sequence, we can implement the failover to any functionality, removing many of the layout restrictions of a cluster:
In the following, we denote the current primary by P1, the current secondary by S1, and the new primary and secondaries by P2 and S2. P2 is fixed to the node the user chooses, but the choice of S2 can be made between P1 and S1. This choice can be constrained, depending on which of P1 and S1 has failed.
The algorithm for performing the failover is straightforward:
Caveats: during the P2?S2 sync, a (non-transient) network error will cause I/O errors on the instance, so (if a longer instance downtime is acceptable) we can postpone the restart of the instance until the resync is done. However, disk I/O errors on S2 will cause data loss, since we don’t have a good copy of the data anymore, so in this case waiting for the sync to complete is not an option. As such, it is recommended that this feature is used only in conjunction with proper disk monitoring.
Live migration note: While failover-to-any is possible for all choices of S2, migration-to-any is possible only if we keep P1 as S2.
The dynamic device model, while more complex, has an advantage: it will not reuse by mistake the DRBD device of another instance, since it always looks for either our own or a free one.
The static one, in contrast, will assume that given a minor number N, it’s ours and we can take over. This needs careful implementation such that if the minor is in use, either we are able to cleanly shut it down, or we abort the startup. Otherwise, it could be that we start syncing between two instance’s disks, causing data loss.
In order to support high-security scenarios (for example read-only sda and read-write sdb), we need to make a fully flexibly disk definition. This has less impact that it might look at first sight: only the instance creation has hard coded number of disks, not the disk handling code. The block device handling and most of the instance handling code is already working with “the instance’s disks” as opposed to “the two disks of the instance”, but some pieces are not (e.g. import/export) and the code needs a review to ensure safety.
The objective is to be able to specify the number of disks at instance creation, and to be able to toggle from read-only to read-write a disk afterward.
Similar to the disk change, we need to allow multiple network interfaces per instance. This will affect the internal code (some function will have to stop assuming that instance.nics is a list of length one), the OS API which currently can export/import only one instance, and the command line interface.
There are two areas of interface changes: API-level changes (the OS interface and the RAPI interface) and the command line interface changes.
The current Ganeti OS interface, version 5, is tailored for Ganeti 1.2. The interface is composed by a series of scripts which get called with certain parameters to perform OS-dependent operations on the cluster. The current scripts are:
Currently these scripts suffer from the limitations of Ganeti 1.2: for example they accept exactly one block and one swap devices to operate on, rather than any amount of generic block devices, they blindly assume that an instance will have just one network interface to operate, they can not be configured to optimise the instance for a particular hypervisor.
Since in Ganeti 2.0 we want to support multiple hypervisors, and a non-fixed number of network and disks the OS interface need to change to transmit the appropriate amount of information about an instance to its managing operating system, when operating on it. Moreover since some old assumptions usually used in OS scripts are no longer valid we need to re-establish a common knowledge on what can be assumed and what cannot be regarding Ganeti environment.
When designing the new OS API our priorities are: - ease of use - future extensibility - ease of porting from the old API - modularity
As such we want to limit the number of scripts that must be written to support an OS, and make it easy to share code between them by uniforming their input. We also will leave the current script structure unchanged, as far as we can, and make a few of the scripts (import, export and rename) optional. Most information will be passed to the script through environment variables, for ease of access and at the same time ease of using only the information a script needs.
As in Ganeti 1.2, every OS which wants to be installed in Ganeti needs to support the following functionality, through scripts:
If any optional script is not implemented Ganeti will refuse to perform the given operation on instances using the non-implementing OS. Of course the create script is mandatory, and it doesn’t make sense to support the either the export or the import operation but not both.
We expect the following incompatibilities between the OS scripts for 1.2 and the ones for 2.0:
Rather than using command line flags, as they do now, scripts will accept inputs from environment variables. We expect the following input values:
These are only the basic variables we are thinking of now, but more may come during the implementation and they will be documented in the ganeti-os-interface(7) man page. All these variables will be available to all scripts.
Some scripts will need a few more information to work. These will have per-script variables, such as for example:
(Rationale for INSTANCE_NAME as an environment variable: the instance name is always needed and we could pass it on the command line. On the other hand, though, this would force scripts to both access the environment and parse the command line, so we’ll move it for uniformity.)
As discussed scripts should only send user-targeted information to stderr. The create and import scripts are supposed to format/initialise the given block devices and install the correct instance data. The export script is supposed to export instance data to stdout in a format understandable by the the import script. The data will be compressed by Ganeti, so no compression should be done. The rename script should only modify the instance’s knowledge of what its name is.
Similar to Ganeti 1.2, OS specifications will need to provide a ‘ganeti_api_version’ containing list of numbers matching the version(s) of the API they implement. Ganeti itself will always be compatible with one version of the API and may maintain backwards compatibility if it’s feasible to do so. The numbers are one-per-line, so an OS supporting both version 5 and version 20 will have a file containing two lines. This is different from Ganeti 1.2, which only supported one version number.
In addition to that an OS will be able to declare that it does support only a subset of the Ganeti hypervisors, by declaring them in the ‘hypervisors’ file.
We might want to have a “default” import/export behaviour that just dumps all disks and restores them. This can save work as most systems will just do this, while allowing flexibility for different systems.
Environment variables are limited in size, but we expect that there will be enough space to store the information we need. If we discover that this is not the case we may want to go to a more complex API such as storing those information on the filesystem and providing the OS script with the path to a file where they are encoded in some format.
The first Ganeti remote API (RAPI) was designed and deployed with the Ganeti 1.2.5 release. That version provide read-only access to the cluster state. Fully functional read-write API demands significant internal changes which will be implemented in version 2.0.
We decided to go with implementing the Ganeti RAPI in a RESTful way, which is aligned with key features we looking. It is simple, stateless, scalable and extensible paradigm of API implementation. As transport it uses HTTP over SSL, and we are implementing it with JSON encoding, but in a way it possible to extend and provide any other one.
The Ganeti RAPI is implemented as independent daemon, running on the same node with the same permission level as Ganeti master daemon. Communication is done through the LUXI library to the master daemon. In order to keep communication asynchronous RAPI processes two types of client requests:
In the query case requested data send back to client in the HTTP response body. Typical examples of queries would be: list of nodes, instances, cluster info, etc.
In the case of job submission, the client receive a job ID, the identifier which allows one to query the job progress in the job queue (see Job Queue).
Internally, each exported object has an version identifier, which is used as a state identifier in the HTTP header E-Tag field for requests/responses to avoid race conditions.
The key difference of using REST instead of others API is that REST requires separation of services via resources with unique URIs. Each of them should have limited amount of state and support standard HTTP methods: GET, POST, DELETE, PUT.
For example in Ganeti’s case we can have a set of URI:
- /{clustername}/instances
- /{clustername}/instances/{instancename}
- /{clustername}/instances/{instancename}/tag
- /{clustername}/tag
A GET request to /{clustername}/instances will return the list of instances, a POST to /{clustername}/instances should create a new instance, a DELETE /{clustername}/instances/{instancename} should delete the instance, a GET /{clustername}/tag should return get cluster tags.
Each resource URI will have a version prefix. The resource IDs are to be determined.
Internal encoding might be JSON, XML, or any other. The JSON encoding fits nicely in Ganeti RAPI needs. The client can request a specific representation via the Accept field in the HTTP header.
REST uses HTTP as its transport and application protocol for resource access. The set of possible responses is a subset of standard HTTP responses.
The statelessness model provides additional reliability and transparency to operations (e.g. only one request needs to be analyzed to understand the in-progress operation, not a sequence of multiple requests/responses).
With the write functionality security becomes a much bigger an issue. The Ganeti RAPI uses basic HTTP authentication on top of an SSL-secured connection to grant access to an exported resource. The password is stored locally in an Apache-style .htpasswd file. Only one level of privileges is supported.
The model detailed above for job submission requires the client to poll periodically for updates to the job; an alternative would be to allow the client to request a callback, or a ‘wait for updates’ call.
The callback model was not considered due to the following two issues:
The ‘wait for updates’ method is not suited to the HTTP protocol, where requests are supposed to be short-lived.
Ganeti 2.0 introduces several new features as well as new ways to handle instance resources like disks or network interfaces. This requires some noticeable changes in the way command line arguments are handled.
The design changes that require these changes are, in no particular order:
As such, there are several areas of Ganeti where the command line arguments will change:
There are several areas of Ganeti where the command line arguments will change:
To avoid problems with device location changes (e.g. second network interface of the instance becoming the first or third and the like) the list of network/disk devices is treated as a stack, i.e. devices can only be added/removed at the end of the list of devices of each class (disk or network) for each instance.
The commands for gnt-instance will be modified and extended to allow for the new functionality:
The generic format of the network device option is:
–net $DEVNUM[:$OPTION=$VALUE][,$OPTION=VALUE]
| $DEVNUM: | device number, unsigned integer, starting at 0, |
|---|---|
| $OPTION: | device option, string, |
| $VALUE: | device option value, string. |
Currently, the following device options will be defined (open to further changes):
| mac: | MAC address of the network interface, accepts either a valid MAC address or the string ‘auto’. If ‘auto’ is specified, a new MAC address will be generated randomly. If the mac device option is not specified, the default value ‘auto’ is assumed. |
|---|---|
| bridge: | network bridge the network interface is connected to. Accepts either a valid bridge name (the specified bridge must exist on the node(s)) as string or the string ‘auto’. If ‘auto’ is specified, the default brigde is used. If the bridge option is not specified, the default value ‘auto’ is assumed. |
The generic format of the disk device option is:
–disk $DEVNUM[:$OPTION=$VALUE][,$OPTION=VALUE]
| $DEVNUM: | device number, unsigned integer, starting at 0, |
|---|---|
| $OPTION: | device option, string, |
| $VALUE: | device option value, string. |
Currently, the following device options will be defined (open to further changes):
| size: | size of the disk device, either a positive number, specifying the disk size in mebibytes, or a number followed by a magnitude suffix (M for mebibytes, G for gibibytes). Also accepts the string ‘auto’ in which case the default disk size will be used. If the size option is not specified, ‘auto’ is assumed. This option is not valid for all disk layout types. |
|---|---|
| access: | access mode of the disk device, a single letter, valid values are:
If the access mode is not specified, the default mode of read/write access will be configured. |
| path: | path to the image file for the disk device, string. No default exists. This option is not valid for all disk layout types. |
To add devices to an already existing instance, use the device type specific option to gnt-instance modify. Currently, there are two device type specific options supported:
| –net: | for network interface cards |
|---|---|
| –disk: | for disk devices |
The syntax to the device specific options is similar to the generic device options, but instead of specifying a device number like for gnt-instance add, you specify the magic string add. The new device will always be appended at the end of the list of devices of this type for the specified instance, e.g. if the instance has disk devices 0,1 and 2, the newly added disk device will be disk device 3.
Example: gnt-instance modify –net add:mac=auto test-instance
Removing devices from and instance is done via gnt-instance modify. The same device specific options as for adding instances are used. Instead of a device number and further device options, only the magic string remove is specified. It will always remove the last device in the list of devices of this type for the instance specified, e.g. if the instance has disk devices 0, 1, 2 and 3, the disk device number 3 will be removed.
Example: gnt-instance modify –net remove test-instance
Modifying devices is also done with device type specific options to the gnt-instance modify command. There are currently two device type options supported:
| –net: | for network interface cards |
|---|---|
| –disk: | for disk devices |
The syntax to the device specific options is similar to the generic device options. The device number you specify identifies the device to be modified.
Example:
gnt-instance modify --disk 2:access=r
Ganeti 2.0 will support more than one hypervisor. Different hypervisors have various options that only apply to a specific hypervisor. Those hypervisor specific options are treated specially via the --hypervisor option. The generic syntax of the hypervisor option is as follows:
--hypervisor $HYPERVISOR:$OPTION=$VALUE[,$OPTION=$VALUE]
| $HYPERVISOR: | symbolic name of the hypervisor to use, string, has to match the supported hypervisors. Example: xen-pvm |
|---|---|
| $OPTION: | hypervisor option name, string |
| $VALUE: | hypervisor option value, string |
The hypervisor option for an instance can be set on instance creation time via the gnt-instance add command. If the hypervisor for an instance is not specified upon instance creation, the default hypervisor will be used.
The hypervisor parameters of an existing instance can be modified using --hypervisor option of the gnt-instance modify command. However, the hypervisor type of an existing instance can not be changed, only the particular hypervisor specific option can be changed. Therefore, the format of the option parameters has been simplified to omit the hypervisor name and only contain the comma separated list of option-value pairs.
Example:
gnt-instance modify --hypervisor cdrom=/srv/boot.iso,boot_order=cdrom:network test-instance
The command for gnt-cluster will be extended to allow setting and changing the default parameters of the cluster:
Cluster defaults
The generic format of the cluster default setting option is:
–defaults $OPTION=$VALUE[,$OPTION=$VALUE]
| $OPTION: | cluster default option, string, |
|---|---|
| $VALUE: | cluster default option value, string. |
Currently, the following cluster default options are defined (open to further changes):
| hypervisor: | the default hypervisor to use for new instances, string. Must be a valid hypervisor known to and supported by the cluster. |
|---|---|
| disksize: | the disksize for newly created instance disks, where applicable. Must be either a positive number, in which case the unit of megabyte is assumed, or a positive number followed by a supported magnitude symbol (M for megabyte or G for gigabyte). |
| bridge: | the default network bridge to use for newly created instance network interfaces, string. Must be a valid bridge name of a bridge existing on the node(s). |
The generic format of the hypervisor cluster wide default setting option is:
--hypervisor-defaults $HYPERVISOR:$OPTION=$VALUE[,$OPTION=$VALUE]
| $HYPERVISOR: | symbolic name of the hypervisor whose defaults you want to set, string |
|---|---|
| $OPTION: | cluster default option, string, |
| $VALUE: | cluster default option value, string. |


African Aquarium
Come on in. There’s something in the water! It’s a colony of black-footed penguins! These tuxedo-clad birds aren’t Antarctic/sub-Antarctic dwellers, but do come from a temperate region of southern Africa, so you won’t see any ice and snow in their home. In fact, it is usually a pleasant 70º in their exhibit. Walking on through the building, there’s more in the water – a big tank of cichlid fish. Behind the tank of fish, tortoises can be seen prowling. Weavers, turacos, plovers and African gray parrots enjoy the space along with a pair of rock hyrax. And there is a glimpse of what’s to come as an African lion is seen through the glass wall...


--- layout: post title: Episode 012 | Jeremy Clark - Understanding Users and Making Developers Better link: http://traffic.libsyn.com/developeronfire/DeveloperOnFire-12-JeremyClark.mp3 length: 68157440 speaker: Dave Rael duration: '47:48' subtitle: 'Developer On Fire: Jeremy Clark' guest_name: Jeremy Clark image: /assets/images/JeremyClark.jpg guest_blog: http://jeremybytes.com guest_twitter: jeremybytes disqus_url: http://developeronfire.com/Podcast/Episodes/jeremy-clark-understanding-users-and-making-developers-better episode_number: 12 book_recommendations: - { url: "http://www.amazon.com/dp/0132350882/?tag=devonfir-20", title: "Clean Code: A Handbook of Agile Software Craftsmanship", author: "Robert C. Martin"} tags: - { url: "http://www.amazon.com/dp/0132350882/?tag=devonfir-20", title: "Clean Code: A Handbook of Agile Software Craftsmanship", author: "Robert C. Martin"} redirect_from: - "/12/" - "/012/" - "/0012/" - "/jeremy-clark-understanding-users-and-making-developers-better/" - "/Jeremy-Clark/" - "/JeremyClark/" - "/Podcast/Episodes/12/" - "/Podcast/Episodes/012/" - "/Podcast/Episodes/0012/" - "/Podcast/Episodes/jeremy-clark-understanding-users-and-making-developers-better/" - "/Podcast/Episodes/Jeremy-Clark/" - "/Podcast/Episodes/JeremyClark/" guid: http://developeronfire.com:80/Podcast/Episodes/jeremy-clark-understanding-users-and-making-developers-better ---


USBMC, just like SD2Vita, is a solution to the expensive Vita memory cards. It allows you to use USB memory with a PSTV, or a microSD card with PSVD boards on 3G Vita 1000s.
Instructions
- Download the latest release of usbmc
- If you haven't already, move your
ux0:tai
folder tour0:tai
and delete theux0:tai
- Install the USBMC
.vpk
you downloaded - Open the USBMC Installer application from the home menu
- Press X to install the drivers and reboot
- Enter the USBMC Installer application again
- Press X to copy the files to your USB or microSD Card
- Make sure you have inserted your USB memory into the PSTV first


Simple Temperature With Thermistor + Arduino
A Thermistor is a thermal-resistor. It’s just a simple device that changes it’s resistance based on temperature. If the LRD/Photoresistor is day of of arduino class. The thermistor should be day 1.01. (Can I do that?).
If you need precise temperature readings, this is not the part for you. Check out the DS18B20, TMP102, or MLX90614
Thermistors are not that precise or anything, so you wont be able to tell the temperature with it, but if you need to know when the temperature has changed, this will work for you. And on the plus side, they are crazy cheap considering the alternatives, incredibly simple to hookup, and have some of the easiest code ever. You can find these pretty easily at most hobby electronics shops, or just add some to your next sparkfun order.
Hooking it up, and why
The thermistor changes its resistance with temperature so we can measure that change using one of the Arduino’s analog pins. But to do that we need a fixed resistor (not changing) that we can use for that comparison (We are using a 10K resistor). This is called a voltage divider and divides the 5v between the thermistor and the resistor.
The analog read on your arduino is basically a voltage meter. at 5V (its max) it would read 1023, and at 0v it read 0. So we can measure how much voltage is on the thermistor using the analogRead and we have our reading.
The amount of that 5V that each part gets is proportional to its resistance. So if the the thermistor and the resistor have the same resistance, the 5V is split evenly (2.5V) to each part. (analog reading of 512)
But if the thermistor is really hot and is reading only 1K of resistance, the 10K resistor is going to soak up 10 times as much of that 5V. So the thermistor would only get .45V. (analog reading of 92)
And if it is in the refrigerator, the thermistor may be 40K or resistance, so the thermistor will soak up 4 times as much of that 5V as the 10K resistor. So the thermistor would get 4V. (analog reading of 819)
Code
The arduino code for this just could not be easier. We are adding some serial prints and delays to it just so you can easily see the readings, but they dont need to be there if you dont need them.
int thermistorPin = A0; //analog pin 0 void setup(){ Serial.begin(9600); } void loop(){ int thermistorReading = analogRead(thermistorPin); Serial.println(thermistorReading); delay(250); //just here to slow down the output for easier reading }


Drop down menu no config given
2nd Drop down menu no config given
Drop down menu with config
4th Drop down menu same config
Same Directive, same config, diff template
Alt Drop down template menu same controller same config
Showing of 'interpolate' filter:
{{ 'Current version is v%VERSION%.' | interpolate }}


The CSS Box Model
All HTML elements can be considered as boxes. In CSS, the term "box model" is used when taking about design and layout.
The CSS Box Model is essentially a box that wraps around every HTML element. It consists of margins, borders, padding, and the actual content. The image below illustrates the box model:
Explanation of the different parts:
Content - The content of the box, where text and images appear.
Padding - Clears an area around the content. The padding is transparent.
Border - A border that goes around the padding and content.
Margin - Clears an area outside the border. The margin is transparent.
The box model allows us to add a border around elements, and to define space between elements.


--- layout: page show_meta: false title: "Carpentries Community Newsletter" header: image_fullwidth: "wood_plank.jpg" permalink: "/newsletter/" ---
Data Carpentry and Software Carpentry put out a joint newsletter each month highlighting happenings in the Carpentry community, job postings, meetup opportunities, and more. To subscribe, sign up here.
Previous issues are available here.


Get started with Docker Compose
Estimated reading time: 10 minutesOn this page you build a simple Python web application running on Docker Compose. The application uses the Flask framework and maintains a hit counter in Redis. While the sample uses Python, the concepts demonstrated here should be understandable even if you’re not familiar with it.
Prerequisites
Make sure you have already installed both Docker Engine and Docker Compose. You don’t need to install Python or Redis, as both are provided by Docker images.
Step 1: Setup
Define the application dependencies.
-
Create a directory for the project:
$ mkdir composetest $ cd composetest
-
Create a file called
app.py
in your project directory and paste this in:import time import redis from flask import Flask app = Flask(__name__) cache = redis.Redis(host='redis', port=6379) def get_hit_count(): retries = 5 while True: try: return cache.incr('hits') except redis.exceptions.ConnectionError as exc: if retries == 0: raise exc retries -= 1 time.sleep(0.5) @app.route('/') def hello(): count = get_hit_count() return 'Hello World! I have been seen {} times.\n'.format(count)
In this example,
redis
is the hostname of the redis container on the application’s network. We use the default port for Redis,6379
.Handling transient errors
Note the way the
get_hit_count
function is written. This basic retry loop lets us attempt our request multiple times if the redis service is not available. This is useful at startup while the application comes online, but also makes our application more resilient if the Redis service needs to be restarted anytime during the app’s lifetime. In a cluster, this also helps handling momentary connection drops between nodes. -
Create another file called
requirements.txt
in your project directory and paste this in:flask redis
Step 2: Create a Dockerfile
In this step, you write a Dockerfile that builds a Docker image. The image contains all the dependencies the Python application requires, including Python itself.
In your project directory, create a file named Dockerfile
and paste the
following:
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST <IP_ADDRESS>
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["flask", "run"]
This tells Docker to:
- Build an image starting with the Python 3.7 image.
- Set the working directory to
/code
. - Set environment variables used by the
flask
command. - Install gcc so Python packages such as MarkupSafe and SQLAlchemy can compile speedups.
- Copy
requirements.txt
and install the Python dependencies. - Copy the current directory
.
in the project to the workdir.
in the image. - Set the default command for the container to
flask run
.
For more information on how to write Dockerfiles, see the Docker user guide and the Dockerfile reference.
Step 3: Define services in a Compose file
Create a file called docker-compose.yml
in your project directory and paste
the following:
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
This Compose file defines two services: web
and redis
.
Web service
The web
service uses an image that’s built from the Dockerfile
in the current directory.
It then binds the container and the host machine to the exposed port, 5000
. This example service uses the default port for
the Flask web server, 5000
.
Redis service
The redis
service uses a public Redis
image pulled from the Docker Hub registry.
Step 4: Build and run your app with Compose
-
From your project directory, start up your application by running
docker-compose up
.$ docker-compose up Creating network "composetest_default" with the default driver Creating composetest_web_1 ... Creating composetest_redis_1 ... Creating composetest_web_1 Creating composetest_redis_1 ... done Attaching to composetest_web_1, composetest_redis_1 web_1 | * Running on http://<IP_ADDRESS>:5000/ (Press CTRL+C to quit) redis_1 | 1:C 17 Aug 22:11:10.480 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo redis_1 | 1:C 17 Aug 22:11:10.480 # Redis version=4.0.1, bits=64, commit=00000000, modified=0, pid=1, just started redis_1 | 1:C 17 Aug 22:11:10.480 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf web_1 | * Restarting with stat redis_1 | 1:M 17 Aug 22:11:10.483 * Running mode=standalone, port=6379. redis_1 | 1:M 17 Aug 22:11:10.483 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. web_1 | * Debugger is active! redis_1 | 1:M 17 Aug 22:11:10.483 # Server initialized redis_1 | 1:M 17 Aug 22:11:10.483 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. web_1 | * Debugger PIN: 330-787-903 redis_1 | 1:M 17 Aug 22:11:10.483 * Ready to accept connections
Compose pulls a Redis image, builds an image for your code, and starts the services you defined. In this case, the code is statically copied into the image at build time.
-
Enter http://localhost:5000/ in a browser to see the application running.
If you’re using Docker natively on Linux, Docker Desktop for Mac, or Docker Desktop for Windows, then the web app should now be listening on port 5000 on your Docker daemon host. Point your web browser to http://localhost:5000 to find the
Hello World
message. If this doesn’t resolve, you can also try http://<IP_ADDRESS>:5000.If you’re using Docker Machine on a Mac or Windows, use
docker-machine ip MACHINE_VM
to get the IP address of your Docker host. Then, openhttp://MACHINE_VM_IP:5000
in a browser.You should see a message in your browser saying:
Hello World! I have been seen 1 times.
-
Refresh the page.
The number should increment.
Hello World! I have been seen 2 times.
-
Switch to another terminal window, and type
docker image ls
to list local images.Listing images at this point should return
redis
andweb
.$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE composetest_web latest e2c21aa48cc1 4 minutes ago 93.8MB python 3.4-alpine 84e6077c7ab6 7 days ago 82.5MB redis alpine 9d8fa9aa0e5b 3 weeks ago 27.5MB
You can inspect images with
docker inspect <tag or id>
. -
Stop the application, either by running
docker-compose down
from within your project directory in the second terminal, or by hitting CTRL+C in the original terminal where you started the app.
Step 5: Edit the Compose file to add a bind mount
Edit docker-compose.yml
in your project directory to add a bind mount for the web
service:
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
environment:
FLASK_ENV: development
redis:
image: "redis:alpine"
The new volumes
key mounts the project directory (current directory) on the
host to /code
inside the container, allowing you to modify the code on the
fly, without having to rebuild the image. The environment
key sets the
FLASK_ENV
environment variable, which tells flask run
to run in development
mode and reload the code on change. This mode should only be used in development.
Step 6: Re-build and run the app with Compose
From your project directory, type docker-compose up
to build the app with the updated Compose file, and run it.
$ docker-compose up
Creating network "composetest_default" with the default driver
Creating composetest_web_1 ...
Creating composetest_redis_1 ...
Creating composetest_web_1
Creating composetest_redis_1 ... done
Attaching to composetest_web_1, composetest_redis_1
web_1 | * Running on http://<IP_ADDRESS>:5000/ (Press CTRL+C to quit)
...
Check the Hello World
message in a web browser again, and refresh to see the
count increment.
Shared folders, volumes, and bind mounts
If your project is outside of the
Users
directory (cd ~
), then you need to share the drive or location of the Dockerfile and volume you are using. If you get runtime errors indicating an application file is not found, a volume mount is denied, or a service cannot start, try enabling file or drive sharing. Volume mounting requires shared drives for projects that live outside ofC:\Users
(Windows) or/Users
(Mac), and is required for any project on Docker Desktop for Windows that uses Linux containers. For more information, see Shared Drives on Docker Desktop for Windows, File sharing on Docker for Mac, and the general examples on how to Manage data in containers.If you are using Oracle VirtualBox on an older Windows OS, you might encounter an issue with shared folders as described in this VB trouble ticket. Newer Windows systems meet the requirements for Docker Desktop for Windows and do not need VirtualBox.
Step 7: Update the application
Because the application code is now mounted into the container using a volume, you can make changes to its code and see the changes instantly, without having to rebuild the image.
-
Change the greeting in
app.py
and save it. For example, change theHello World!
message toHello from Docker!
:return 'Hello from Docker! I have been seen {} times.\n'.format(count)
-
Refresh the app in your browser. The greeting should be updated, and the counter should still be incrementing.
Step 8: Experiment with some other commands
If you want to run your services in the background, you can pass the -d
flag
(for “detached” mode) to docker-compose up
and use docker-compose ps
to
see what is currently running:
$ docker-compose up -d
Starting composetest_redis_1...
Starting composetest_web_1...
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------
composetest_redis_1 /usr/local/bin/run Up
composetest_web_1 /bin/sh -c python app.py Up 5000->5000/tcp
The docker-compose run
command allows you to run one-off commands for your
services. For example, to see what environment variables are available to the
web
service:
$ docker-compose run web env
See docker-compose --help
to see other available commands. You can also install command completion for the bash and zsh shell, which also shows you available commands.
If you started Compose with docker-compose up -d
, stop
your services once you’ve finished with them:
$ docker-compose stop
You can bring everything down, removing the containers entirely, with the down
command. Pass --volumes
to also remove the data volume used by the Redis
container:
$ docker-compose down --volumes
At this point, you have seen the basics of how Compose works.
Where to go next
- Next, try the quick start guide for Django, Rails, or WordPress
- Explore the full list of Compose commands
- Compose configuration file reference
- To learn more about volumes and bind mounts, see Manage data in Docker


Home Office Design System
Design System for Home Office Digital, Data and Technology (DDaT). It extends the GOV.UK system with patterns and components for additional use cases and users. The intent is to help create a common design language amongst delivery teams, making it cheaper to build and scale products and services.
There is a Design System Working Group who are responsible for accepting new patterns. If you’ve got a question or suggestion share it on Slack #interaction-designers channel or in a GitHub issue.
How to use the design system
When designing services you should consider this framework:
- all services - whether for public or internal users - must start by using GOV.UK design elements and patterns
- if you find those don’t meet your users needs, refer to the Home Office Design system and the cross-Government Design Patterns wiki to see if the problem has been approached
- if you need to design something new because there’s no existing pattern let us know on Slack #interaction-designers channel or in a GitHub issue


Jul 28, 2015 5:01 PM
Over the years, Imagine Easy Solutions, the parent company of EasyBib, has released numerous white papers on how their users conduct research and practical ethical writing. With over 90 million student users across Imagine Easy's properties, we have the unique opportunity to delve deeper into how students find information, synthesize it, and use in class assignments. Through independent research and partnerships with national research groups, we've identified major paint points that students face when working on writing assignments. Upon analyzing this data, our team has developed a brand new product, Imagine Easy Scholar, to support students throughout the entire reading, research, and writing processes. This seminar will provide an overview of key findings on how students approach writing and research--stemming from our own research and third-party surveys conducted via Imagine Easy's websites. We will also provide an in-depth overview of Imagine Easy Scholar, including its interactive Chrome Extension and Google Docs Add-on, to demonstrate how Scholar supports students (and educators) in 21st century reading, writing, and research. We truly build our products based off of user habits, research, and teacher feedback. Emily Gover is the Community Manager at Imagine Easy Solutions. She has worked with the company since 2012, joining the team as their first on-staff librarian. She has written curriculum, developed content, and now works with the thousands of educators around the world involved with Imagine Easy's community to better understand how tech can be used in instruction and learning. Kathy Krupa has a passion for education and has worked with students and educators over the past 12 years. She works closely educators from across the globe supporting them with new and innovative ways to make a difference in the classroom. Imagine Easy Solutions, established in 2001, creates imaginative, easy-to-use services for teachers and students to promote 21st century writing, research and critical thinking skills, which are instrumental for success in school and career.


Provides interfaces and classes in Vaadin.
Interface hierarchy
The general interface hierarchy looks like this:
Note that the above picture includes only the main interfaces. This package includes several other lesser sub-interfaces which are not significant in this scope. The interfaces not appearing here are documented with the classes that define them.
The {@link com.vaadin.ui.Component} interface is the top-level interface which must be implemented by all user interface components in Vaadin. It defines the common properties of the components and how the framework will handle them. Most simple components, such as {@link com.vaadin.ui.Button}, for example, do not need to implement the lower-level interfaces described below. Notice that also the classes and interfaces required by the component event framework are defined in {@link com.vaadin.ui.Component}.
The next level in the component hierarchy are the classes implementing the {@link com.vaadin.ui.ComponentContainer} interface. It adds the capacity to contain other components to {@link com.vaadin.ui.Component} with a simple API.
The third and last level is the {@link com.vaadin.ui.Layout}, which adds the concept of location to the components contained in a {@link com.vaadin.ui.ComponentContainer}. It can be used to create containers which contents can be positioned.
Component class hierarchy
The actual component classes form a hierarchy like this:
At the top level is {@link com.vaadin.ui.AbstractComponent} which
implements the {@link com.vaadin.ui.Component} interface. As the name
suggests it is abstract, but it does include a default implementation
for all methods defined in Component
so that a component is
free to override only those functionalities it needs.
As seen in the picture, AbstractComponent
serves as
the superclass for several "real" components, but it also has a some
abstract extensions. {@link com.vaadin.ui.AbstractComponentContainer}
serves as the root class for all components (for example, panels and
windows) who can contain other components. {@link
com.vaadin.ui.AbstractField}, on the other hand, implements several
interfaces to provide a base class for components that are used for data
display and manipulation.


Cavs general manager Koby Altman signs multiyear extension
by TOM WITHERSCLEVELAND (AP) - Cavaliers general manager Koby Altman has signed a multiyear contract extension.
Details were not immediately available Friday. The 37-year-old Altman signed a three-year contract in 2017, not long after he took over when the club parted with previous GM David Griffin.
Altman is the first GM to receive an extension under owner Dan Gilbert, who bought the Cavs from Gordon Gund in 2005.
He will continue to oversee a rebuild that began when LeBron James left as a free agent in 2017 after leading Cleveland to its fourth straight NBA Finals appearance.
The Cavs went just 19-63 during an injury-ravaged 2018-19 season. They are off to a 2-2 start this year under first-year coach John Beilein.
Altman has been acquiring future draft picks. He’s also orchestrated contract extensions for Kevin Love, Larry Nance Jr. and Cedi Osman.
More AP NBA: https://apnews.com/NBA and https://twitter.com/AP_Sports
Sign up for Daily Newsletters
Copyright © 2019 The Washington Times, LLC.
Please read our comment policy before commenting.


WrappedJavaProcess first starts a Controller which is waiting for the
application to connect to it's server port.
Then the application java process is started using jna.
The
java process executes WrapperMain.main() within the application's class
loader. This main method first creates the WrapperManagerProxy, which
loads the WrapperManagerImpl singleton within a separate class loader.
We can thus ensure that the application will not be "influenced" by any
classes loaded within the WrapperMangerImpl.
Once initialiazed
the WrapperManagerImpl pipes the java input output and error streams to
temporary circular buffer files and then connects to the
Controller server port, thus indicating to the wrapper that the child
process has successfully been spawned. The temprorary files are
read/written by the WrappedJavaProcess class. We can thus read and
write to the application streams from within the wrapper process.
Then the main() method of the application is called.
The
application can access the WrapperManager for example to request a
shutdown of the process by calling WrapperMain.WRAPPER_MANAGER.stop().