1use std::net::{Ipv4Addr, Ipv6Addr};
12
13use indexmap::IndexMap;
14use isoloom_spec::images;
15use isoloom_spec::model::{Arch, Boot, Constraints, External, Machine, MachineFile, Network, Protocol, Spec, Target, Tc, Ulimit, VmImage, Winrm};
16use isoloom_spec::validate::docker_cidrs;
17use isoloom_spec::windows::{self, Tpm};
18
19mod image;
20
21pub use image::{Build, Image};
22
23use crate::addressing::{address, address_for, host_address, start_order};
24use crate::ansible;
25use crate::checks::{self, Resolved};
26use crate::configure::{DaemonTable, Recipe};
27use crate::hardware::{self, Hardware};
28use crate::images::Images;
29use crate::ipv6::{self, Ipv6Plan, Route6};
30use crate::lifecycle::{self, MachineLifecycle};
31use crate::outside::{self, OutsideAddress, OutsideNetwork};
32use crate::process::Process;
33use crate::requirements::{self, Requirement};
34use crate::routing;
35use crate::secrets::SecretFile;
36use crate::storage::{self, DataDisk, Lifetime};
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct Environment {
41 pub name: String,
43 pub target: Target,
45 pub instance: Option<u8>,
47 pub networks: IndexMap<String, NetworkPlan>,
49 pub outside: IndexMap<String, OutsideNetwork>,
52 pub router: Option<IndexMap<String, Ipv4Addr>>,
54 pub router6: IndexMap<String, Ipv6Addr>,
56 pub machines: IndexMap<String, MachinePlan>,
58 pub start_order: Vec<String>,
60 pub checks: Vec<Resolved>,
62 pub controller: Option<Controller>,
64 pub constraints: Option<Constraints>,
66 pub requirements: Vec<Requirement>,
68 pub images: Images,
70 pub inventory_groups: IndexMap<String, Vec<String>>,
72 pub private_dns: Option<crate::private_dns::PrivateDns>,
74 spec: Spec,
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub struct NetworkPlan {
80 pub cidr: String,
82 pub declared_cidr: String,
84 pub host: Ipv4Addr,
86 pub gateway: Option<String>,
88 pub internet: bool,
90 pub tc: Option<Tc>,
92 pub vlan: Option<(String, u16)>,
94 pub ipv6: Option<Ipv6Plan>,
96 pub mtu: Option<u16>,
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum MachineShape {
103 Container,
105 Vm,
107 Supplied,
109 External,
111}
112
113#[derive(Debug, Clone, PartialEq)]
115pub struct MachinePlan {
116 pub shape: MachineShape,
118 pub addresses: IndexMap<String, Ipv4Addr>,
120 pub more_addresses: IndexMap<String, Vec<Ipv4Addr>>,
123 pub addresses6: IndexMap<String, Ipv6Addr>,
125 pub names: Vec<String>,
128 pub routes: Vec<Route>,
130 pub routes6: Vec<Route6>,
132 pub default_gateway: Option<Ipv4Addr>,
134 pub is_gateway: bool,
136 pub offline: bool,
138 pub ports: Vec<PublishedPort>,
140 pub volumes: Vec<Volume>,
142 pub files: Vec<MachineFile>,
144 pub disks: Vec<DataDisk>,
146 pub settings: Settings,
148 pub inputs: Vec<String>,
150 pub secrets: Vec<SecretFile>,
152 pub size: Size,
154 pub arch: Arch,
156 pub rate_limit: Option<String>,
158 pub image: Option<Image>,
160 pub vm_image: Option<VmImage>,
163 pub boot: Option<Boot>,
165 pub depends_on: Vec<String>,
167 pub lifecycle: MachineLifecycle,
169 pub os: Option<String>,
172 pub winrm: Option<Winrm>,
174 pub winssh: bool,
176 pub tpm: Option<Tpm>,
178 pub external: Option<External>,
180 pub host_vars: IndexMap<String, serde_json::Value>,
182 pub outside: IndexMap<String, OutsideAddress>,
184 pub process: Process,
186 pub hardware: Hardware,
188 pub recipe: Option<Recipe>,
191}
192
193impl MachinePlan {
194 pub fn is_windows(&self) -> bool {
196 self.os.as_deref().is_some_and(images::is_windows)
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct Route {
203 pub cidr: String,
205 pub via: Ipv4Addr,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct PublishedPort {
212 pub name: String,
214 pub port: u16,
216 pub publish: u16,
218 pub protocol: Protocol,
220 pub fixed: bool,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct Volume {
227 pub name: String,
229 pub path: String,
231 pub subpath: Option<String>,
233 pub key: String,
235 pub lifetime: Lifetime,
237 pub kind: VolumeKind,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
243pub enum VolumeKind {
244 Own,
246 Shared {
248 owner: String,
250 users: Vec<String>,
252 },
253}
254
255#[derive(Debug, Clone, Default, PartialEq, Eq)]
257pub struct Settings {
258 pub environment: IndexMap<String, String>,
260 pub sysctls: IndexMap<String, String>,
262 pub ulimits: IndexMap<String, Ulimit>,
264}
265
266impl Settings {
267 #[must_use]
269 pub fn is_empty(&self) -> bool {
270 self.environment.is_empty() && self.sysctls.is_empty() && self.ulimits.is_empty()
271 }
272}
273
274#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
276pub struct Size {
277 pub cpus: Option<u32>,
279 pub memory_mb: Option<u32>,
281 pub disk_gb: Option<u32>,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
287#[non_exhaustive]
288pub struct Controller {
289 pub cpus: u32,
291 pub memory_mb: u32,
293 pub disk_gb: Option<u32>,
295}
296
297impl Controller {
298 pub const CPUS: u32 = 1;
300 pub const MEMORY_MB: u32 = 512;
302
303 #[must_use]
306 pub fn of(spec: &Spec) -> Controller {
307 let r = spec.controller.as_ref().and_then(|c| c.resources.as_ref());
308 Controller {
309 cpus: r.and_then(|r| r.cpus).unwrap_or(Controller::CPUS),
310 memory_mb: r.and_then(|r| r.memory_mb).unwrap_or(Controller::MEMORY_MB),
311 disk_gb: r.and_then(|r| r.disk_gb),
312 }
313 }
314
315 #[must_use]
317 pub fn needed(spec: &Spec) -> bool {
318 !spec.provision.is_empty() || spec.checks.iter().any(isoloom_spec::Check::is_playbook)
319 }
320}
321
322impl Environment {
323 #[must_use]
325 pub fn build(spec: &Spec, target: Target, instance: Option<u8>) -> Environment {
326 let laid_out = outside::without(&lay_out(spec, target));
328 let machines = machines(spec, &laid_out, target);
329 let requirements = all_requirements(spec, &laid_out, &machines);
330 let networks = laid_out
331 .networks
332 .iter()
333 .map(|(name, n)| {
334 let declared = spec.networks.get(name).map_or(&n.cidr, |o| &o.cidr);
335 (name.clone(), network(&laid_out, name, n, declared))
336 })
337 .collect();
338 Environment {
339 name: laid_out.name.clone(),
340 target,
341 instance,
342 networks,
343 outside: outside::networks(spec),
344 router: routing::needed(&laid_out).then(|| routing::networks(&laid_out).map(|n| (n.clone(), routing::address(&laid_out, n))).collect()),
345 router6: ipv6::router(&laid_out),
346 machines,
347 start_order: start_order(&laid_out).into_iter().map(String::from).collect(),
348 checks: checks::plan(&laid_out),
349 controller: Controller::needed(&laid_out).then(|| Controller::of(&laid_out)),
350 constraints: laid_out.constraints.clone(),
351 requirements,
352 images: Images::of(&laid_out),
353 inventory_groups: ansible::groups(&laid_out),
354 private_dns: crate::private_dns::plan(&laid_out),
355 spec: laid_out,
356 }
357 }
358
359 #[must_use]
361 pub fn spec(&self) -> &Spec {
362 &self.spec
363 }
364
365 #[must_use]
367 pub fn address_for(&self, from: &str, to: &str) -> Ipv4Addr {
368 address_for(&self.spec, from, to)
369 }
370
371 #[must_use]
376 pub fn probe_address(&self, name: &str, network: &str) -> String {
377 let m = self.machines.get(name);
378 let address = if ipv6::only(&self.spec, network) {
379 m.and_then(|m| m.addresses6.get(network)).map(ToString::to_string)
380 } else {
381 m.and_then(|m| m.addresses.get(network)).map(ToString::to_string)
382 };
383 address.unwrap_or_else(|| name.to_owned())
384 }
385}
386
387#[must_use]
389pub fn on_docker(target: Target) -> bool {
390 matches!(target, Target::Docker | Target::Hosted)
391}
392
393#[must_use]
395pub fn lay_out(spec: &Spec, target: Target) -> Spec {
396 let mut s = spec.clone();
397 if on_docker(target) {
398 for (name, c) in docker_cidrs(spec) {
399 if let Some(n) = s.networks.get_mut(&name) {
400 n.cidr = c.to_string();
401 }
402 }
403 }
404 s
405}
406
407fn network(spec: &Spec, name: &str, n: &Network, declared: &str) -> NetworkPlan {
409 NetworkPlan {
410 cidr: n.cidr.clone(),
411 declared_cidr: declared.to_owned(),
412 host: host_address(spec, name),
413 gateway: n.gateway.clone(),
414 internet: n.internet,
415 tc: n.tc.clone(),
416 vlan: n.vlan.clone(),
417 ipv6: ipv6::network(spec, name),
418 mtu: n.mtu,
419 }
420}
421
422fn shape(target: Target, m: &Machine) -> MachineShape {
426 if target == Target::External {
427 return MachineShape::External;
428 }
429 let container = match target {
430 Target::Docker | Target::Hosted | Target::DockerVm | Target::CloudDocker | Target::Kubernetes => true,
431 Target::Hybrid => m.docker.is_some(),
432 Target::Proxmox => m.vm.is_none() && m.docker.as_ref().is_some_and(|d| d.lxc),
434 _ => false,
435 };
436 match (container, m.docker.is_some(), m.vm.is_some()) {
437 (true, true, _) => MachineShape::Container,
438 (false, _, true) => MachineShape::Vm,
439 _ => MachineShape::Supplied,
440 }
441}
442
443fn machines(spec: &Spec, laid_out: &Spec, target: Target) -> IndexMap<String, MachinePlan> {
445 laid_out
446 .machines
447 .iter()
448 .map(|(name, m)| {
449 let mut mp = machine(laid_out, target, name, m);
450 mp.outside = spec.machines.get(name).map(|om| outside::addresses(spec, om)).unwrap_or_default();
451 (name.clone(), mp)
452 })
453 .collect()
454}
455
456fn all_requirements(spec: &Spec, laid_out: &Spec, machines: &IndexMap<String, MachinePlan>) -> Vec<Requirement> {
459 let shape = |n: &str| machines.get(n).map(|m| m.shape);
460 let mut out = requirements::of(laid_out);
461 out.extend(outside::requirements(spec));
462 out.extend(requirements::mixed_shapes(laid_out, shape));
463 out.extend(crate::machine_files::requirements(laid_out, shape));
464 out.extend(requirements::tpm(machines));
465 out.extend(crate::boot::requirements(machines));
466 out.extend(hardware::requirements(laid_out, shape));
467 out.extend(crate::more_addresses::requirements(laid_out, shape));
468 out
469}
470
471fn machine(spec: &Spec, target: Target, name: &str, m: &Machine) -> MachinePlan {
473 let shape = shape(target, m);
474 let recipe = m.configuration.as_ref().and_then(|c| Recipe::of(c, &m.ports, &DaemonTable::common()).ok());
476 MachinePlan {
477 shape,
478 addresses: m.networks.iter().map(|(n, o)| (n.clone(), address(spec, n, *o))).collect(),
479 more_addresses: crate::more_addresses::of(spec, m),
480 addresses6: ipv6::addresses(spec, m),
481 names: crate::private_dns::with_domain(
482 spec,
483 std::iter::once(name).chain(m.aliases.iter().map(String::as_str)).map(String::from).collect(),
484 ),
485 routes: routing::routes(spec, name, m).into_iter().map(|(cidr, via)| Route { cidr, via }).collect(),
486 routes6: if target == Target::CloudVm { Vec::new() } else { ipv6::routes(spec, m) },
487 default_gateway: routing::default_gateway(spec, name, m),
488 is_gateway: routing::is_gateway(spec, name),
489 offline: routing::offline(spec, name, m),
490 ports: published(m),
491 volumes: volumes(spec, name, m),
492 files: m.files.clone(),
493 disks: storage::disks(name, m),
494 settings: Settings {
495 environment: m.environment.clone(),
496 sysctls: m.sysctls.clone(),
497 ulimits: m.ulimits.clone(),
498 },
499 inputs: m.inputs.clone(),
500 secrets: m.secrets.iter().map(|s| SecretFile::of(spec, s)).collect(),
501 size: m
502 .resources
503 .map(|r| Size {
504 cpus: r.cpus,
505 memory_mb: r.memory_mb,
506 disk_gb: r.disk_gb,
507 })
508 .unwrap_or_default(),
509 arch: m.arch,
510 rate_limit: m.rate_limit.clone(),
511 image: image::of(spec, shape, name, m, recipe.as_ref()),
512 vm_image: m.vm.as_ref().and_then(|v| v.image.clone()),
513 boot: m.vm.as_ref().and_then(|v| v.boot.clone()),
514 depends_on: m.depends_on.clone(),
515 lifecycle: if shape == MachineShape::Vm {
516 vm_lifecycle(spec, target, name, m, recipe.as_ref())
517 } else {
518 Vec::new()
519 },
520 os: m.vm.as_ref().map(|v| v.os.clone()),
521 winrm: m.vm.as_ref().filter(|v| images::is_windows(&v.os) && !windows::winssh(v)).map(images::winrm),
522 winssh: m.vm.as_ref().is_some_and(windows::winssh),
523 tpm: m.vm.as_ref().and_then(windows::tpm),
524 external: m.external.clone(),
525 host_vars: ansible::host_vars(spec, name),
526 outside: IndexMap::new(),
527 process: Process::of(m),
528 hardware: Hardware::of(m),
529 recipe,
530 }
531}
532
533fn vm_lifecycle(spec: &Spec, target: Target, name: &str, m: &Machine, recipe: Option<&Recipe>) -> MachineLifecycle {
535 let mut steps = lifecycle::of(spec, name, m, recipe);
536 if target == Target::CloudVm {
537 ipv6::cloud_routes(&mut steps);
538 }
539 steps
540}
541
542fn published(m: &Machine) -> Vec<PublishedPort> {
543 m.ports
544 .iter()
545 .filter_map(|s| {
546 s.publish.map(|publish| PublishedPort {
547 name: m.port_label(s),
548 port: s.port,
549 publish,
550 protocol: s.protocol,
551 fixed: s.fixed,
552 })
553 })
554 .collect()
555}
556
557fn volumes(spec: &Spec, name: &str, m: &Machine) -> Vec<Volume> {
558 m.volumes
559 .iter()
560 .map(|(v, mount)| Volume {
561 name: v.clone(),
562 path: mount.path().to_owned(),
563 subpath: mount.subpath().map(String::from),
564 key: spec.volume_key(name, v),
565 lifetime: storage::lifetime(spec, v),
566 kind: match spec.volume_owner(v) {
568 Some(owner) => VolumeKind::Shared {
569 owner: owner.to_owned(),
570 users: spec.volume_users(v).into_iter().map(String::from).collect(),
571 },
572 None => VolumeKind::Own,
573 },
574 })
575 .collect()
576}
577
578#[cfg(test)]
579mod tests {
580 use isoloom_spec::model::Target;
581
582 use super::{Controller, Environment};
583 use crate::testing;
584
585 const NEEDS_ONE: &str = "version: 1\nname: t\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n web: { networks: { lan: 10 }, os: debian@12 }\nprovision: [{ ansible: site.yml }]\n";
586
587 fn controller(extra: &str) -> Option<Controller> {
588 testing::environment(&format!("{NEEDS_ONE}{extra}"), Target::Vagrant).controller
589 }
590
591 #[test]
592 fn the_controller_has_one_cpu_and_512_mb_unless_the_spec_sizes_it() {
593 assert_eq!(
594 controller(""),
595 Some(Controller {
596 cpus: 1,
597 memory_mb: 512,
598 disk_gb: None
599 })
600 );
601 assert_eq!(
602 controller("controller: { resources: { cpus: 2, memory_mb: 768, disk_gb: 10 } }\n"),
603 Some(Controller {
604 cpus: 2,
605 memory_mb: 768,
606 disk_gb: Some(10)
607 })
608 );
609 }
610
611 #[test]
612 fn settings_are_empty_until_one_is_set() {
613 assert!(super::Settings::default().is_empty());
614 let mut s = super::Settings::default();
615 s.sysctls.insert("net.ipv4.ip_forward".into(), "1".into());
616 assert!(!s.is_empty());
617 let mut u = super::Settings::default();
618 u.environment.insert("A".into(), "1".into());
619 assert!(!u.is_empty());
620 }
621
622 #[test]
623 fn a_probe_reaches_a_machine_at_its_address_else_by_name() {
624 let env = testing::environment(
625 "version: 1\nname: t\nnetworks:\n v4: { cidr: 10.9.0.0/24 }\n v6: { cidr: 10.8.0.0/24, ipv6: { cidr: \"fd00:8::/64\", only: true } }\n auto: { cidr: 10.7.0.0/24, ipv6: { cidr: \"fd00:7::/64\", only: true, addresses: auto } }\nmachines:\n web: { networks: { v4: 10, v6: 10, auto: 10 }, app: { image: nginx } }\n",
626 Target::Kubernetes,
627 );
628 assert_eq!(env.probe_address("web", "v4"), "10.9.0.10");
629 assert_eq!(env.probe_address("web", "v6"), "fd00:8::10");
630 assert_eq!(env.probe_address("web", "auto"), "web");
631 assert_eq!(env.probe_address("ghost", "v4"), "ghost");
632 }
633
634 const SHAPES: &str = "version: 1
637name: t
638networks: { a: { cidr: 10.1.0.0/24, ipv6: { cidr: \"fd00:1::/64\" } }, b: { cidr: 10.2.0.0/24, ipv6: { cidr: \"fd00:2::/64\" } } }
639reach: [{ from: a, to: b }]
640volumes: { data: { owner: vm } }
641machines:
642 both: { networks: { a: 10 }, os: debian@12, app: { image: nginx, steps: [{ run: a.sh }] }, volumes: { data: /d, own: /o } }
643 img: { networks: { a: 11 }, app: { image: nginx } }
644 vm: { networks: { b: 12 }, os: debian@12, app: { steps: [{ run: a.sh }, reboot] }, volumes: { data: /d } }
645 ct: { networks: { a: 13 }, app: { image: alpine } }
646 win: { networks: { a: 14 }, os: windows-server@2022 }
647 user: { access: true, networks: { a: 5 } }
648overrides:
649 proxmox: { machines: { ct: { lxc: true } } }
650 vagrant: { machines: { win: { winrm: ssl } } }
651";
652
653 fn shapes(target: Target) -> Vec<(String, super::MachineShape)> {
654 testing::environment(SHAPES, target)
655 .machines
656 .iter()
657 .map(|(n, m)| (n.clone(), m.shape))
658 .collect()
659 }
660
661 fn shape_of(target: Target, machine: &str) -> Option<super::MachineShape> {
662 shapes(target).into_iter().find(|(n, _)| n == machine).map(|(_, s)| s)
663 }
664
665 #[test]
666 fn each_target_produces_a_machine_in_the_form_it_runs() {
667 use super::MachineShape::{Container, External, Supplied, Vm};
668 for t in [Target::Docker, Target::Hosted, Target::DockerVm, Target::CloudDocker, Target::Kubernetes] {
669 assert_eq!(shape_of(t, "both"), Some(Container), "{t:?}");
670 assert_eq!(shape_of(t, "user"), Some(Supplied), "{t:?}");
671 }
672 assert_eq!(shape_of(Target::Hybrid, "both"), Some(Container), "a container where it has one");
673 assert_eq!(shape_of(Target::Hybrid, "vm"), Some(Vm));
674 assert_eq!(shape_of(Target::Proxmox, "ct"), Some(Container), "an LXC container");
675 assert_eq!(shape_of(Target::Proxmox, "both"), Some(Vm));
676 assert_eq!(
677 shape_of(Target::Proxmox, "img"),
678 Some(Supplied),
679 "no VM and no LXC: Proxmox refuses it, nothing is built"
680 );
681 assert_eq!(shape_of(Target::Vagrant, "both"), Some(Vm));
682 assert_eq!(shape_of(Target::External, "both"), Some(External));
683 }
684
685 #[test]
686 fn a_vm_has_set_up_steps_and_a_container_none() {
687 let env = testing::environment(SHAPES, Target::Hybrid);
688 let steps = |m: &str| env.machines.get(m).map_or(0, |p| p.lifecycle.len());
689 assert!(steps("vm") > 0);
690 assert_eq!(steps("both"), 0);
691 }
692
693 #[test]
694 fn a_cloud_routes_ipv6_itself_and_the_other_vm_targets_through_the_router() {
695 let routes6 = |t: Target| {
696 testing::environment(SHAPES, t)
697 .machines
698 .get("both")
699 .map(|m| m.routes6.len())
700 .unwrap_or_default()
701 };
702 assert_eq!(routes6(Target::Vagrant), 1);
703 assert_eq!(routes6(Target::CloudVm), 0);
704 let cloud = testing::environment(SHAPES, Target::CloudVm);
705 let ipv6_routes = cloud.machines.get("both").and_then(|m| {
706 m.lifecycle.iter().find_map(|s| match s {
707 crate::Step::Ipv6 { routes, .. } => Some(routes.len()),
708 _ => None,
709 })
710 });
711 assert_eq!(ipv6_routes, Some(0));
712 }
713
714 #[test]
715 fn a_shared_volume_names_its_owner_and_users_and_an_own_one_is_the_machines() {
716 let env = testing::environment(SHAPES, Target::Vagrant);
717 let both = env.machines.get("both").map(|m| m.volumes.clone()).unwrap_or_default();
718 let kinds: Vec<(&str, &super::VolumeKind)> = both.iter().map(|v| (v.name.as_str(), &v.kind)).collect();
719 assert_eq!(
720 kinds,
721 [
722 (
723 "data",
724 &super::VolumeKind::Shared {
725 owner: "vm".into(),
726 users: vec!["both".into(), "vm".into()]
727 }
728 ),
729 ("own", &super::VolumeKind::Own)
730 ]
731 );
732 }
733
734 #[test]
735 fn a_windows_vm_answers_winrm_its_way() {
736 let env = testing::environment(SHAPES, Target::Vagrant);
737 let win = env.machines.get("win").expect("planned");
738 assert!(win.is_windows() && !win.winssh);
739 assert_eq!(win.winrm, Some(isoloom_spec::model::Winrm::Ssl));
740 let vm = env.machines.get("vm").expect("planned");
741 assert!(!vm.is_windows() && vm.winrm.is_none());
742 assert!(super::on_docker(Target::Docker) && super::on_docker(Target::Hosted) && !super::on_docker(Target::DockerVm));
743 }
744
745 #[test]
746 fn only_playbooks_need_a_controller() {
747 let lab = testing::lab(
748 "version: 1\nname: t\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n web: { networks: { lan: 10 }, os: debian@12 }\nchecks: [checks/a.sh]\n",
749 );
750 let spec = lab.for_target(Target::Vagrant);
751 assert!(!Controller::needed(&spec) && Environment::build(&spec, Target::Vagrant, None).controller.is_none());
752 let playbook = testing::lab(
753 "version: 1\nname: t\nnetworks: { lan: { cidr: 10.9.0.0/24 } }\nmachines:\n web: { networks: { lan: 10 }, os: debian@12 }\nchecks: [checks/a.yml]\n",
754 );
755 assert!(Controller::needed(&playbook.for_target(Target::Vagrant)));
756 assert_eq!(Controller::of(&spec).memory_mb, Controller::MEMORY_MB);
757 }
758}