This draft is based on my current understanding, which may well be limited/incomplete/wrong. It presents subjective recommendations and opinions which may be flawed and/or subject to change.
Provide a special VM instance that is readily available to experiment/test/demonstrate.
- Overall: daemon, clients (CLI & GUI), and possibly installer/snap packaging.
- For now: daemon, CLI (not GUI), and possibly installer/snap packaging.
Based on the references below, a summary of current intentions for this feature could be:
- Current name:
primary
primary
created automatically (upon installation, current default image)- Users may delete the primary instance
- Users may set which instance is the primary one
- Commands operate on
primary
by default (i.e. if no target instance is specified)
In the simplest scenario, the lifetime of the primary instance closely matches multipass’s own installation-lifetime. The daemon creates the pet instance ASAP after it first starts and keeps it indefinitely (this accomplishes proposition 2 above, as the daemon is launched when multipass is installed). Uninstalling multipass finally removes primary
along with any other existing instances (stopping the daemon stops any running VMs and uninstalling removes persistified data).
However, a primary instance may also be deleted. Moreover, a different instance may be set as primary (although a maximum of one primary instance is supported at any one time). Creation/destruction and set-primary/unset-primary times are discussed below (not to be confused with start/stop times).
In any case, multipass needs to function properly with and without a primary instance.
Preconditions may be unfulfilled when the daemon first starts and tries to create the primary instance (e.g. no network to download the image, not enough disk space).
Decision: What should we do in this case? Options:
- Ignore and keep going without
primary
- Keep going without
primary
, retry periodically until success
Note: 1 is easier than 2 and pretty much contained in it. So, if we want 2, perhaps it can be done in a separate issue/PR. This is the approach I would favor.
Warning: IIUC, multipass is already supposed to handle multiple client requests simultaneously (it did respond well to quick smoke tests with concurrent but orthogonal requests). However, there could be other concurrency issues when a client is used right away, while primary
is still launching (see below).
Should the setup of primary
be synchronous with installation, or asynchronous? That is, should the installation wait until primary
is setup before completing? Current design discussions indicate not, IIUC, but the feedback to the user may be less than ideal in that case.
Ideally the user would be notified that an instance was being created/downloaded/launched. They probably want to know that a substantial download is under way and that they have a VM setup. This is especially important if the instance is automatically started (see below).
Decision: do we rely only on the tray indicator for this? Is the tray/GUI client meant to be shipped within the same multipass package? What happens in text-only mode? Do we leave the user uninformed until he tries to attach to the instance? Options:
- info message in installation -> would be easily missed; I think each snap installation message is erased by the one that follows? Can we print a "sticky" message (one that moves output to a new line)?
- creation process synchronous with installation; e.g. installer outputs creation progress, waiting for it to finish -> would certainly make it noticeable, but delay installation.
- option during installation ("Do you want to start a primary instance? [Y/n]") -> I guess those should be kept to a minimum(?) Some deb packages have such interactive installation, but I haven’t seen it in a snap...
Options 2 and 3 above would effectively make the installer a client of the multipass daemon. Perhaps an indirect one, if it called the CLI client and echoed its output. This has implications on how much new functionality needs to be exposed as interface.
In any case, I suggest starting with 1, but together with option 1 in Create vs Launch (that is, creating but not starting primary
. The other options here could be separately implemented, later on, if so decided.
Note: any of the options above would expand the scope to the installer level (snap), even if the impact of option 1 would be limited to a single output message.
Decision: the instance should be created upon installation, but do we also want to start it? Options:
- No -> A separate create operation would have to be added. Currently, multipassd provides no operation to simply create an instance without starting it. This could be extracted from the current
launch
, which would internally become a combination ofcreate
andstart
(that is, conceptually, what launching is). A separate decision would be whether to exposecreate
to clients (so they could offer acreate
command). - Yes -> Feedback to the user becomes especially important; extra step needed to stop VM if not needed;
I would favor option 1.
The user may delete a primary instance with a regular delete request. I suggest it retains the primary status after deleted and until it is purged or the status removed/changed explicitly. All instances are removed at uninstallation at the latest.
The daemon needs to extend its interface, to provide a way to set an instance as primary. This can be achieved with a new request, say set-primary
, with a single existing instance as argument. The operation would move the primary status from any previous instance holding it, in an atomized step, to the new intended primary instance (including persistification).
By atomized step I mean that other threads should not see intermediate states. In other words, concurrent threads serving multiple set-primary
client requests should be synchronized and execute those operations sequentially. A consistent state, with a single primary instance, needs to prevail in the end. In practice this can be done with a lock around this critical code.
Warning: I am uneasy about other operations’ current thread safety. For instance, what happens if two clients try to delete and recover an instance concurrently? Looking at the code, this does not seem to be covered. It was probably never noticed because typical usage currently employs a single client. But that will change once we have a tray icon/GUI. Also, concurrent requests will become more likely if primary
is created asynchronously: the user may start using multipass right after installation, while primary
is still being created.
Decision: should the launch
request (and create
, if we decide to add it) provide a knob to set the new instance as the primary one? Options:
- Yes -> that would provide the interface to create
primary
with a single request, which would be useful if we opted for the synchronous alternatives in Feedback to the user. The commands would have to consider thread-safety - No, only allow setting existing instances as primary -> two steps required to achieve the same thing
I think I would favor 1, but perhaps as a later addition.
Decision: do we provide a new "knob" to remove the primary status of an instance? Or can this state only be overwritten? Options:
- Yes: new request, say
unset-primary
-> one more interface element to support, more functionality to synchronize; one more command to list in help may contribute to obscure essentials - No -> the user may feel "uncared for" (he can still achieve the same thing by setting a new instance as primary before deleting it)
I think I would favor 2 here.
The term primary
is currently used in both the instance name and to distinguish the status of an instance that receives special treatment. But the possibility to set/unset arbitrary instances as primary introduces a simetry break: the status is moved, but the name isn’t. This can lead to confusion, especially when primary
is no longer primary. A simple solution is to use different terms for instance name and status.
Decision: what terms do we use? Options:
- Name the primary instance
primary
but start calling something else to the status (e.g. "default") -> but aprimary
instance that was no longer the default could still be confusing - Give the instance some other fixed name (e.g.
default
) and continue using "primary" for the status -> analogous problem, although perhaps slightly better - Name the instance variably, with a pet name, just like any other instance, still reserving the term "primary" for the status -> makes it clear that primary status cannot be determined from name
- Rename instances dynamically when setting primary status, so that the current primary instance would always be called
primary
-> would introduce a lot of complication in the way instance records are kept in memory and disk and it would still be confusing to have the nameprimary
designate different instances at different times.
I favor option 3.
Note: this document still uses primary
to refer to the primary instance in many places
Many multipass commands receive the instances they target as arguments. Commands that support multiple instances also provide the flag --all
to target all instances. An instance argument is required unless the flag --all
is present (e.g. multipass start --all
).
Baseline proposition 5 means that commands with neither instance arguments nor the --all
flag would target the primary instance (if available). So something like multipass stop
would mean the same as multipass stop primary
.
However, client requests with an empty instance field are currently interpreted by the daemon to mean that all instances are to be targeted. So, to accomplish this default-instance feature, one of two things would need to happen:
- the logic of checking for a default target and selecting it would have to be placed in the client
- the client/server communication protocol would have to be changed in all such requests
From these options, I favor the second, but I wonder...
Decision: is this default instance behavior really what we want? I am not sure the pros compensate the cons, especially when considering that:
- if only
primary
exists, auto-complete already selects it with a simple tab key press - if other instances exist, muscle memory could cause people to target
primary
by mistake primary
will often not be an appropriate default. For instance, it would not be appropriate to saymultipass start
ifprimary
was trashed; ormultipass purge
unlessprimary
was deleted; ormultipass info
if no primary was set -> A default that does not account for that makes it easy to try wrong things. And only the daemon is in the proper place to discern that. Unless the client made preliminary requests... Again, autocomplete already deals with this sort of thing- if we removed this default targeting functionality, the only thing distinguishing a
primary
instance would be that it was created automatically! Primary status would otherwise be irrelevant, so it would make no sense to support moving it. That would greatly simplify this feature: much of the complexity discussed here would disappear - I think the "default instance" functionality has no counterpart in the GUI client, so the effect of setting an instance as primary may be unclear to someone using only the GUI
In my opinion, we should strive for simplicity here. Are the additional complications worth the occasional typing economy? I am not trying to argue for something like Apple’s single button approach, which I don’t really like (everyone has edge cases once in a while). But a balance should still be struck. I find that removing the "default instance" feature could actually contribute more to a "slick" multipass usage experience than the other way around.
Detail: if the user wants to target primary
in a command that also targets other instances, they have to mention it explicitly. It is perhaps worth highlighting that a command may have 2 more arguments than another and yet only target one additional instance. This is a potential pitfall for both developers and users: it is easy to mistake the number of arguments with the number of targets (e.g. imagine a script’s maintainer trying to add an additional target to an exiting command-line variable).
Note: this document still deals with the current state of affairs: a Pet Environment feature that includes the default instance.
The instance with primary status has to be persistified. Since only one is allowed at any point, this could be achieved with a separate field, which could either be empty or specify a single instance. The field should be saved whenever the corresponding status changes in memory (i.e. set/unset primary, launch, etc.).
Canceling an instance creation procedure is mentioned a few times in multipass-design. While this could be supported by multipassd, this is not currently the case. SIGINT on CLI only aborts the client, but the daemon keeps handling the request. Implementing this would require actions to be transactional, so they could be rolled back, lest we get into inconsistent intermediate states. It would be a big feature on its own.
The command itself is unaffected. The documentation it relies upon needs to change in the following ways:
- If we create new commands, they need to be documented
- If we want to target
primary
by default, change the documentation of all affected commands to explain this, mentioning that it only works if the instance exists (and that other requirements still apply)
No direct impact, just accompany new functionality with appropriate logging.
If we decide to target primary
by default in other commands, we may choose to abbreviate primary:<path>
as :<path>
here. Omitting the instance still requires a leading colon, to distinguish which end of the copy is inside the instance (multipass copy-files /a/path /another/path
would be ambiguous).
The command would fail if we defaulted to primary
and its state was not RUNNING
.
A default instance would be applicable here if so decided.
Decision: would we allow a default also with --purge
? Options:
- Yes -> dangerous, too punishing on stray-enter mistake
- No -> another special case, another asymmetry
The command would be a no-op if we defaulted to primary
and its state was DELETED
.
A default instance would be applicable here if so decided. If so, then the argument --
becomes mandatory when no instance argument is provided, to distinguish where the command begins. In other words, the first free argument would still be an instance name when before --
.
The command would fail if we defaulted to primary
and its state was not RUNNING
.
No impact I can think of.
See --help
The output should clearly indicate when an instance was tagged as primary. The primary instance should also be listed first, when part of the output.
A default instance here could confuse expectations. In particular, a distracted user may expect multipass info
to encompass all instances.
Note: today, output order simply follows argument order.
If we decide to support launching an instance as primary (see launch as primary) this command would accept an additional flag (e.g. --set-primary
).
Note: this command receives a free argument specifying an image. That argument already has a default, but here it designates the image rather than an instance. This means that there is a small asymmetry relatively to the other commands, which may present confusing affordance clues. The asymmetry is not introduced by the default-instance feature on this occasion – it is already there. But its impact may be aggravated. In particular, the unfamiliar user may confuse launch
with start
and expect multipass launch
to mean multipass start [primary]
, although they are quite different.
The primary instance should be listed in the first place.
Decision should the output somehow highlight the primary instance? Options:
- No -> ...but it would be nice :)
- Yes, with a dedicated column -> but the output table is already quite wide...
- Yes, with a single character mark (perhaps '*'?) -> would have to be documented; where to put it? beginning or end of name?
I would favor option 3, end of name.
As in copy-files
, if we decide to target primary
by default in other commands, we may choose to abbreviate primary:<path>
as :<path>
here. Unlike in the former command, mount endpoints are distinguished by position, so the leading colon would not be strictly required. But I suggest maintaining it, for consistency and clarity.
The command would fail if we defaulted to primary
and its state was not RUNNING
.
A default instance would be applicable here if so decided.
Decision: do we allow a default here in particular? Options:
- Yes -> dangerous, too punishing on stray-enter mistake
- No -> another special case, another asymmetry
The command would fail if we defaulted to primary
and its state was not DELETED
.
A default instance would be applicable here if so decided.
The command would be a no-op
if we defaulted to primary
and its state was not DELETED
.
A default instance would be applicable here if so decided.
The command would fail if we defaulted to primary
and its state was not RUNNING
(actually depends on the outcome of this issue.
A default instance would be applicable here if so decided.
The command would fail if we defaulted to primary
and its state was not RUNNING
.
A default instance would be applicable here if so decided.
The command would be a no-op
if we defaulted to primary
and its state was RUNNING
. The command would fail if we defaulted to primary
and its state was DELETED
.
A default instance would be applicable here if so decided.
The command would be a no-op
if we defaulted to primary
and its state was either STOPPED
or SUSPENDED
. The command would fail if we defaulted to primary
and its state was DELETED
.
A default instance would be applicable here if so decided.
The command would be a no-op
if we defaulted to primary
and its state was SUSPENDED
or STOPPED
. The command would fail if we defaulted to primary
and its state was DELETED
.
As in copy-files
, if we decide to target primary
by default in other commands, we may choose to abbreviate primary:<path>
as :<path>
here. In this case, and since we may already omit paths to unmount all mounts in an instance, we should also support multipass umount
to unmount all mount points in the primary instance. Omitting the instance still requires the leading colon, for clarity and consistency with copy-files
, and to provide a quick way to distinguish instance names from paths.
The command would be a no-op
if we defaulted to primary
and there were no mount points in that instance. The command would fail if we defaulted to primary
and its state was DELETED
(unless this issue turns out to be valid).
No impact.
If it was added, this would have the same interface as launch
, except it would not start the created instance. See the Create vs Launch discussion above.
This receives a single free argument, identifying an existing instance to set s primary. Whether we require that instance not to be deleted should match what we do in umount
.
See the section set primary above.
If this was added, it would not receive any arguments and it would remove the primary status from the instance holding it, if any (no-op
otherwise). Whether we require that instance not to be deleted should match what we do in umount
.
- https://github.com/CanonicalLtd/multipass-design/issues/18
- https://github.com/CanonicalLtd/multipass-design/issues/19
- https://github.com/CanonicalLtd/multipass-design/issues/20
- https://github.com/CanonicalLtd/multipass-design/issues/21
- https://github.com/CanonicalLtd/multipass-design/issues/22
- https://docs.google.com/document/d/1NW53qrqJDCfjOgpcwoptsO9-L1sO-IInD-dIKEkpGJc/edit#heading=h.jgqsclo3jmsi