kkcloud_framework/
model.rs

1//!
2//! # Design Model
3//!
4//! Design of the core infrastructure.
5//!
6
7use crate::err::*;
8use ruc::{err::*, *};
9use std::{
10    collections::{HashMap, HashSet},
11    fmt::Debug,
12    fs, mem,
13    path::{Path, PathBuf},
14    sync::Arc,
15};
16
17/// Use `u64` to express an ID,
18/// e.g. [EnvId](self::Env::id), [VmId](self::Vm::id) ...
19pub type Id = u64;
20
21/// ID alias for ENV.
22pub type EnvId = Id;
23
24/// ID alias for VM.
25pub type VmId = Id;
26
27/// Alias of user name.
28pub type UserId = String;
29
30/// Use `u16` to express a socket port.
31pub type SockPort = u16;
32
33/// Service ports within the host machine.
34pub type PubSockPort = SockPort;
35
36/// Service ports within the VM.
37pub type InnerSockPort = SockPort;
38
39/// Use `String` to express a network address,
40/// in the the perspective of the client, see [NetAddr](self::Vm::addr).
41pub type NetAddr = String;
42
43/// Inner IP(v4) address of VM.
44pub type IpAddr = [u8; 4];
45
46/// MAC address of VM.
47pub type MacAddr = [u8; 6];
48
49/// Supported features of vm-engines.
50#[derive(Debug)]
51#[non_exhaustive]
52pub enum VmFeature {
53    /// If snapshot is supported.
54    Snapshot,
55    /// If start/stop(aka pause) is supported.
56    StartStop,
57    /// If [Nat](self::NetKind::Nat) is supported.
58    NatNetwork,
59    /// If [Flatten](self::NetKind::Flatten) is supported.
60    FlatNetwork,
61}
62
63/// Kind of network.
64#[derive(Debug)]
65#[non_exhaustive]
66pub enum NetKind {
67    /// Like VxLan or Flannel(used in k8s).
68    Flatten,
69    /// Need firewall to do ports-forwarding.
70    Forward,
71    /// An alias of [Forward](self::NetKind::Forward).
72    Nat,
73}
74
75impl Default for NetKind {
76    fn default() -> Self {
77        Self::Nat
78    }
79}
80
81/// User -> [Env ...] -> [[Vm ...], ...]
82pub struct User {
83    /// Aka "user name".
84    pub id: UserId,
85    /// Optional password for a user.
86    pub passwd: Option<String>,
87    /// All envs belong to this user.
88    pub env_set: HashSet<EnvId>,
89}
90
91/// The base unit of KK,
92/// stands for a complete workspace for client.
93#[derive(Debug, Default)]
94pub struct Env {
95    /// UUID of the ENV
96    pub id: Id,
97    /// Name of the ENV.
98    pub name: Option<String>,
99    /// The start timestamp of this ENV,
100    /// can NOT be changed.
101    pub start_timestamp: u64,
102    /// The end timestamp of this ENV,
103    /// permit use to change it .
104    pub end_timestamp: u64,
105    /// All [VmId](self::Vm::id)s under this ENV.
106    pub vm_set: HashSet<Id>,
107    /// Info about the state of ENV.
108    pub state: EnvState,
109}
110
111/// Info about the state of ENV.
112#[derive(Debug, Default)]
113pub struct EnvState {
114    /// Whether this ENV is stopped.
115    pub is_stopped: bool,
116    /// If true, all VMs of this ENV are denied to Internet.
117    pub deny_outgoing: bool,
118    /// The timestamp of the lastest manage-operation,
119    /// such as 'stop/start/snapshot...'.
120    ///
121    /// This kind of operation can NOT be called frequently,
122    /// because them will take a long time to complete.
123    pub last_mgmt_timestamp: u64,
124}
125
126/// Infomations about a VM instance.
127#[derive(Debug)]
128pub struct Vm {
129    /// UUID of this `VM`
130    pub id: Id,
131    /// Name of the `VM`.
132    pub name: Option<String>,
133    /// Created by which engine.
134    pub engine: Arc<dyn VmEngine>,
135    /// Template of `runtime_image`, that is,
136    /// the runtime image is created based on the template.
137    pub template: Arc<VmTemplate>,
138    /// Runtime image of Vm.
139    ///
140    /// Use 'String' instead of 'PathBuf', because
141    /// `runtime_image` may not be a regular file path,
142    /// such as `ZFS` stroage.
143    ///
144    /// E.g. zroot/kk/[VmId](self::Vm::id)
145    pub runtime_image: String,
146    /// Network kind of this VM.
147    pub net_kind: NetKind,
148    /// SnapshotName => Snapshot
149    pub snapshots: HashMap<String, Snapshot>,
150    /// The latest cached config-file.
151    pub latest_meta: Option<PathBuf>,
152    /// Info about the state of VM.
153    pub state: VmState,
154    /// Info about the resource of VM.
155    pub resource: VmResource,
156    /// Usually an 'IP' or a 'domain url'.
157    ///
158    /// Only meaningful from the perspective of the client,
159    /// to indicate how to connect to it from the client.
160    ///
161    /// This has different meanings with the
162    /// [ip_addr](self::VmResource::ip_addr) in [VmResource](self::VmResource).
163    pub addr: NetAddr,
164    /// Features required by this vm.
165    pub features: HashSet<VmFeature>,
166}
167
168/// Infomations about the template of VM,
169/// or in other word, the base image of VM.
170#[derive(Debug, Default)]
171pub struct VmTemplate {
172    /// Globally unique name,
173    /// e.g. "github.com/ktmlm/alpine".
174    pub name: String,
175    /// Path which pointing to the template.
176    /// May not be a regular file path, such as `ZFS`.
177    pub path: String,
178    /// Description of the template image, that is,
179    /// the low-level infrastructure of the runtime image.
180    pub memo: Option<String>,
181    /// Engines that can use this template, e.g. 'Qemu'.
182    pub compatible_engines: HashSet<String>,
183}
184
185/// Info about the state of VM.
186#[derive(Debug, Default)]
187pub struct VmState {
188    /// Whether has been stopped.
189    pub during_stop: bool,
190    /// Whether keep image NOT to be destroyed,
191    /// when the life cycle of `Vm` ends.
192    pub keep_image: bool,
193    /// Whether generate a random uuid for this VM,
194    /// if NOT, `machine-id` of the VM may be empty.
195    pub rand_uuid: bool,
196    /// VM can NOT connect to the addrs in this list.
197    pub net_blacklist: HashSet<IpAddr>,
198}
199
200/// Info about the resource of VM.
201#[derive(Debug, Default)]
202pub struct VmResource {
203    /// CPU number
204    pub cpu_num: u16,
205    /// Memory size in MB
206    pub mem_size: u32,
207    /// Disk size in MB
208    pub disk_size: u32,
209    /// Inner IP address, e.g. '[10,0,0,2]',
210    /// IP is generated from 'MAC address',
211    /// use the last three fields of MAC.
212    pub ip_addr: IpAddr,
213    /// MAC address, e.g. '[0x82,0x17,0x0d,0x6a,0xbc,0x80]',
214    /// used to generate the responding IP address.
215    pub mac_addr: MacAddr,
216    /// Ports allocation for NAT, that is:
217    /// {Private Port within VM} => {Public Port within Host}.
218    ///
219    /// If the type of network is [Flatten](self::NetKind::Flatten),
220    /// this field should be empty(and be ignored).
221    pub port_map: HashMap<InnerSockPort, PubSockPort>,
222}
223
224/// Snapshot management.
225#[derive(Debug, Default)]
226pub struct Snapshot {
227    /// The name of snapshot.
228    pub name: String,
229    /// The data path of snapshot,
230    /// May not be a regular file path, such as `ZFS`.
231    pub path: String,
232    /// The corresponding metadata to the snapshot.
233    pub meta_path: PathBuf,
234    /// If set this, snapshot will be cleaned when
235    /// the number of seconds it exists exceeds this value.
236    pub life_time: Option<u64>,
237}
238
239impl Snapshot {
240    /// Init a new snapshot instance without `life_time` limition.
241    pub fn new(name: String, meta_path: PathBuf) -> Self {
242        Self::newx(name, None, meta_path)
243    }
244
245    /// Init a new snapshot instance.
246    pub fn newx(name: String, life_time: Option<u64>, meta_path: PathBuf) -> Self {
247        Snapshot {
248            name,
249            life_time,
250            path: String::new(),
251            meta_path,
252        }
253    }
254}
255
256/// Common methods for each engine,
257/// such as 'Firecracker', 'Qemu', 'Docker' ...
258pub trait VmEngine: Send + Sync + Debug + Network + Storage {
259    /// Will be called once during system starting.
260    fn init() -> Result<Arc<dyn VmEngine>>
261    where
262        Self: Sized;
263
264    /// Get the name of engine.
265    fn name(&self) -> &str;
266
267    /// Check if all wanted features can be supported.
268    fn ok_features(&self, vm: &Vm) -> bool;
269
270    /// Get all features supported by this engine.
271    fn get_features(&self) -> &'static [VmFeature];
272
273    /// Create the VM instance, and update necessary data of the `Vm`.
274    fn create_vm(&self, vm: &mut Vm) -> Result<()> {
275        self.create_image(vm)
276            .c(e!(ERR_KK_STORAGE_CREATE_IMAGE))
277            .and_then(|_| self.set_net(vm).c(e!(ERR_KK_NET_SET_NET)))
278    }
279
280    /// Destroy the VM instance, and update necessary data of the `Vm`.
281    fn destroy_vm(&self, vm: &mut Vm) -> Result<()> {
282        self.destroy_image(vm)
283            .c(e!(ERR_KK_STORAGE_DESTROY_IMAGE))
284            .and_then(|_| self.unset_net(vm).c(e!(ERR_KK_NET_UNSET_NET)))
285    }
286
287    /// Start a `stopped` VM.
288    fn start_vm(&self, vm: &mut Vm) -> Result<()>;
289
290    /// Stop(aka pause) a running VM.
291    fn stop_vm(&self, vm: &mut Vm) -> Result<()>;
292
293    /// Use the new `Vm` instead of the old one,
294    /// apply all configs in the new `Vm`.
295    fn update_vm(&mut self, vm: Vm) -> Result<()>;
296
297    /// Cache all infomations of the 'Vm' to disk.
298    fn cache_meta(&self, vm: &Vm) -> Result<PathBuf>;
299
300    /// Remove the cached config of `Vm`.
301    fn clean_meta(&self, vm: &mut Vm, path: &Path) -> Result<()> {
302        fs::remove_file(path).c(e!(ERR_KK_SYS_IO)).map(|_| {
303            if let Some(ref p) = vm.latest_meta {
304                if p == path {
305                    vm.latest_meta = None;
306                }
307            }
308        })
309    }
310
311    /// Restruct a `Vm` from a cached config.
312    fn restore_from_meta(&self, path: &Path) -> Result<Vm>;
313
314    /// Add a snapshot for the runtime image:
315    ///
316    /// 1. stop the runtime instance
317    /// 2. cache current meta-config
318    /// 3. snapshot storage
319    /// 4. restart the runtime instance
320    fn create_snapshot(
321        &self,
322        vm: &mut Vm,
323        name: &str,
324        life_time: Option<u64>,
325    ) -> Result<()> {
326        self.stop_vm(vm)
327            .c(e!(ERR_KK_STOP_VM))
328            .and_then(|_| {
329                self.cache_meta(vm)
330                    .c(e!(ERR_KK_META_CREATE_CACHE))
331                    .and_then(|meta| {
332                        let mut snapshot =
333                            Snapshot::newx(name.to_owned(), life_time, meta);
334                        <Self as Storage>::create_snapshot(self, vm, &snapshot)
335                            .c(e!(ERR_KK_SNAPSHOT_CREATE))
336                            .map(|path| {
337                                snapshot.path = path;
338                                vm.snapshots.insert(name.to_owned(), snapshot);
339                            })
340                    })
341            })
342            .and_then(|_| self.start_vm(vm).c(e!(ERR_KK_START_VM)))
343    }
344
345    /// Delete a snapshot of the runtime image:
346    ///
347    /// 1. remove the storage of snapshot
348    /// 2. remove the cached-meta of snapshot
349    fn destroy_snapshot(&self, vm: &mut Vm, name: &str) -> Result<()> {
350        vm.snapshots.remove(name).ok_or(eg!()).and_then(|snapshot| {
351            <Self as Storage>::destroy_snapshot(self, vm, &snapshot.path)
352                .c(e!(ERR_KK_SNAPSHOT_DESTROY))
353                .and_then(|_| {
354                    self.clean_meta(vm, &snapshot.meta_path)
355                        .c(e!(ERR_KK_META_REMOVE_CACHE))
356                })
357        })
358    }
359
360    /// Revert to the state of this snapshot:
361    ///
362    /// 1. stop the runtime instance
363    /// 3. relink runtime image to the one in snapshot
364    /// 2. restore the responding [Vm](self::Vm) from cached-meta
365    /// 4. restart the runtime instance
366    fn apply_snapshot(&mut self, vm: &mut Vm, name: &str) -> Result<()> {
367        self.stop_vm(vm)
368            .and_then(|_| {
369                let snapshot = vm.snapshots.get(name).ok_or(eg!())?;
370                let mut cached_vm = self
371                    .restore_from_meta(&snapshot.meta_path)
372                    .c(e!(ERR_KK_META_RESTORE_CACHE))?;
373                <Self as Storage>::apply_snapshot(self, &cached_vm, &snapshot)
374                    .c(e!(ERR_KK_SNAPSHOT_APPLY))?;
375                cached_vm.snapshots = mem::take(&mut vm.snapshots);
376                self.update_vm(cached_vm).c(e!(ERR_KK_UPDATE_VM))
377            })
378            .and_then(|_| self.start_vm(vm).c(e!(ERR_KK_START_VM)))
379    }
380}
381
382/// This trait describes how to manage the network,
383/// such as 'firewall rule' in the [NAT](self::NetKind::Nat) mode.
384pub trait Network {
385    /// Set network for the VM.
386    fn set_net(&self, vm: &mut Vm) -> Result<()>;
387    /// Unset network for the VM.
388    fn unset_net(&self, vm: &mut Vm) -> Result<()>;
389
390    /// Disable VM's active access to the Internet.
391    fn deny_outgoing(&self, vm: &mut Vm) -> Result<()>;
392    /// Enable VM's active access to the Internet.
393    fn allow_outgoing(&self, vm: &mut Vm) -> Result<()>;
394
395    /// There needs NOT a reponsponding `unset_` method,
396    /// we can get an equal effect by clear the [net_blacklist](self::VmState::net_blacklist).
397    fn set_blacklist(&self, vm: &mut Vm) -> Result<()>;
398}
399
400/// This trait describes how to manage the 'runtime image'.
401pub trait Storage {
402    /// Create a runtime image from it's template.
403    fn create_image(&self, vm: &mut Vm) -> Result<()>;
404    /// Destroy a runtime image.
405    fn destroy_image(&self, vm: &mut Vm) -> Result<()>;
406
407    /// Add a snapshot for the runtime image.
408    fn create_snapshot(&self, vm: &mut Vm, snapshot: &Snapshot) -> Result<String>;
409    /// Delete a snapshot of the runtime image.
410    fn destroy_snapshot(&self, vm: &Vm, snapshot_path: &str) -> Result<()>;
411    /// Revert to the state of this snapshot.
412    fn apply_snapshot(&self, vm: &Vm, snapshot: &Snapshot) -> Result<()>;
413}