Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file albatross_cli.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468(* (c) 2018 Hannes Mehnert, all rights reserved *)openVmm_coreopenLwt.Infixletprocess=Metrics.field~doc:"name of the process""vm"Metrics.Stringletinit_influxnamedata=matchdatawith|None->()|Some(ip,port)->Logs.info(funm->m"stats connecting to %a:%d"Ipaddr.ppipport);Metrics.enable_all();Metrics_lwt.init_periodic(fun()->Lwt_unix.sleep10.);Metrics_lwt.periodically(Metrics_rusage.rusage_src~tags:[]);Metrics_lwt.periodically(Metrics_rusage.kinfo_mem_src~tags:[]);letget_cache,reporter=Metrics.cache_reporter()inMetrics.set_reporterreporter;letfd=refNoneinletrecreport()=letsend()=(match!fdwith|Some_->Lwt.return_unit|None->letaddr=Lwt_unix.ADDR_INET(Ipaddr_unix.to_inet_addrip,port)andfam=Lwt_unix.(matchipwithIpaddr.V4_->PF_INET|Ipaddr.V6_->PF_INET6)inVmm_lwt.connectfamaddr>|=function|None->Logs.err(funm->m"connection failure to stats")|Somefd'->fd:=Somefd')>>=fun()->match!fdwith|None->Lwt.return_unit|Somesocket->lettag=processnameinletdatas=Metrics.SM.fold(funsrc(tags,data)acc->letname=Metrics.Src.namesrcinMetrics_influx.encode_line_protocol(tag::tags)dataname::acc)(get_cache())[]inletdatas=String.concat""datasinVmm_lwt.write_rawsocket(Bytes.unsafe_of_stringdatas)>|=function|Ok()->()|Error`Exception->Logs.warn(funm->m"error on stats write");fd:=Noneandsleep()=Lwt_unix.sleep10.inLwt.join[send();sleep()]>>=reportinLwt.asyncreporttypeexit_status=|Success|Local_authentication_failed|Remote_authentication_failed|Communication_failed|Connect_failed|Remote_command_failed|Cli_failed|Internal_error|Http_errorletoutput_result((hdr,reply)aswire)=letverbose=matchLogs.level()withSomeLogs.Debug->true|_->falseinmatchreplywith|`Successs->Logs.app(funm->m"%a"(Vmm_commands.pp_wire~verbose)wire);letwrite_to_filenamecompresseddata=letfilename=letts=Ptime.to_rfc3339(Ptime_clock.now())inFpath.(v(Filename.get_temp_dir_name())/Name.to_stringname+ts)inletwritedata=matchBos.OS.File.writefilenamedatawith|Ok()->Logs.app(funm->m"dumped image to %a"Fpath.ppfilename)|Error(`Msge)->Logs.err(funm->m"failed to write image: %s"e)inifcompressedthenmatchVmm_compress.uncompress(Cstruct.to_stringdata)with|Okblob->writeblob|Error`Msgmsg->Logs.err(funm->m"failed to uncompress image: %s"msg)elsewrite(Cstruct.to_stringdata)inbeginmatchswith|`Unikernel_image(compressed,image)->letname=hdr.Vmm_commands.nameinwrite_to_filenamecompressedimage|`Old_unikernelsvms->List.iter(fun(name,cfg)->ifCstruct.lengthcfg.Unikernel.image>0thenwrite_to_filenamecfg.Unikernel.compressedcfg.Unikernel.image)vms|`Block_device_image(compressed,image)->letname=hdr.Vmm_commands.nameinwrite_to_filenamecompressedimage|_->()end;Ok()|`Data_->Logs.app(funm->m"%a"(Vmm_commands.pp_wire~verbose)wire);Ok()|`Failure_->Logs.warn(funm->m"%a"(Vmm_commands.pp_wire~verbose)wire);ErrorRemote_command_failed|`Command_->Logs.err(funm->m"received unexpected command %a"(Vmm_commands.pp_wire~verbose)wire);ErrorInternal_errorletsetup_logstyle_rendererlevel=Fmt_tty.setup_std_outputs?style_renderer();Logs.set_levellevel;Logs.set_reporter(Logs_fmt.reporter~dst:Format.std_formatter())letcreate_vmforceimagecpuidmemoryargvblock_devicesbridgescompressionrestart_on_failexit_codes=let(let*)=Result.bindinletimg_file=Fpath.vimageinlet*image=Bos.OS.File.readimg_fileinlet*()=Vmm_unix.manifest_devices_match~bridges~block_devices(Cstruct.of_stringimage)inletimage,compressed=matchcompressionwith|0->Cstruct.of_stringimage,false|level->letimg=Vmm_compress.compress~levelimageinCstruct.of_stringimg,trueandargv=matchargvwith[]->None|xs->Somexsandfail_behaviour=letexits=matchexit_codeswith[]->None|xs->Some(IS.of_listxs)inifrestart_on_failthen`Restartexitselse`Quitinletconfig={Unikernel.typ=`Solo5;compressed;image;fail_behaviour;cpuid;memory;block_devices;bridges;argv}inifforcethenOk(`Unikernel_force_createconfig)elseOk(`Unikernel_createconfig)letcreate_blocksizecompressiondata=let(let*)=Result.bindinmatchdatawith|None->Ok(`Block_add(size,false,None))|Someimage->let*size_in_mb=Vmm_unix.bytes_of_mbsizeinifsize_in_mb>=Cstruct.lengthimagethenletcompressed,img=ifcompression>0thentrue,Vmm_compress.compress_cscompressionimageelsefalse,imageinOk(`Block_add(size,compressed,Someimg))elseError(`Msg"data exceeds size")letpolicyvmsmemorycpusblockbridges=letbridges=String_set.of_listbridgesandcpuids=IS.of_listcpusinPolicy.{vms;cpuids;memory;block;bridges}openCmdlinerletsetup_log=Term.(constsetup_log$Fmt_cli.style_renderer()$Logs_cli.level())letip_port=letppppf(ip,port)=Format.fprintfppf"%a:%d"Ipaddr.ppipportinArg.conv(Ipaddr.with_port_of_string~default:8094,pp)letinflux=letdoc="IP address and port (default: 8094) to report metrics to in influx line protocol"inArg.(value&opt(someip_port)None&info["influx"]~doc~docv:"INFLUXHOST[:PORT]")lethost_port=letparses=matchString.split_on_char':'swith|[hostname;port]->begintryOk(hostname,int_of_stringport)withNot_found->Error(`Msg"failed to parse port")end|_->Error(`Msg"broken: no port specified")inArg.conv(parse,funppf(h,p)->Format.fprintfppf"%s:%d"hp)letvm_c=Arg.conv(Name.of_string,Name.pp)letbridge_tap_c=letparses=matchString.split_on_char':'swith|[bridge;tap]->Ok(bridge,tap)|_->Error(`Msg"broken, format is bridge:tap")andppppf(bridge,tap)=Format.fprintfppf"%s:%s"bridgetapinArg.conv(parse,pp)letbridge_taps=letdoc="Bridge and tap device names"inArg.(value&opt_allbridge_tap_c[]&info["bridge"]~doc)letpid_req1=letdoc="Process id"inArg.(required&pos1(someint)None&info[]~doc~docv:"PID")letvmm_dev_req0=letdoc="VMM device name"inArg.(required&pos0(somestring)None&info[]~doc~docv:"VMMDEV")letopt_vm_name=letdoc="name of virtual machine."inArg.(value&optvm_cName.root&info["n";"name"]~doc)leturi_c=letparses=matchString.split_on_char'/'swith|("http:"|"https:")::""::_host::[]->Oks|("http:"|"https:")::""::_host::""::[]->Ok(String.subs0(String.lengths-1))|_->Error(`Msg("expected http[s]://hostname"))inArg.conv(parse,Fmt.string)(* https://builds.robur.coop/ or https://builds.robur.coop *)lethttp_host=letdoc="Base-URL of binary unikernel repository."inArg.(value&opturi_c"https://builds.robur.coop"&info["http-host"]~doc)letcompress_leveldefault=letdoc="Compression level (0 - 9), a higher value results in smaller data, but uses more CPU "inArg.(value&optintdefault&info["compression-level"]~doc)letforce=letdoc="force VM creation."inArg.(value&flag&info["f";"force"]~doc)letdryrun=letdoc="dry run - do not make any changes."inArg.(value&flag&info["dryrun"]~doc)letcpus=letdoc="CPUids to allow"inArg.(value&opt_allint[]&info["cpu"]~doc)letvms=letdoc="Number of VMs to allow"inArg.(required&pos1(someint)None&info[]~doc~docv:"VMS")letimage=letdoc="File of virtual machine image."inArg.(required&pos1(somefile)None&info[]~doc~docv:"IMAGE")letvm_name=letdoc="Name virtual machine."inArg.(required&pos0(somevm_c)None&info[]~doc~docv:"VM")letblock_name=letdoc="Name of block device."inArg.(required&pos0(somevm_c)None&info[]~doc~docv:"BLOCK")letblock_size=letdoc="Block size in MB."inArg.(required&pos1(someint)None&info[]~doc~docv:"SIZE")letdata_c=letparses=Result.mapCstruct.of_string(Bos.OS.File.read(Fpath.vs))andppppfdata=Format.fprintfppf"file with %d bytes"(Cstruct.lengthdata)inArg.conv(parse,pp)letblock_data=letdoc="Block device content."inArg.(required&pos1(somedata_c)None&info[]~doc~docv:"FILE")letopt_block_data=letdoc="Block device content."inArg.(value&opt(somedata_c)None&info["data"]~doc~docv:"FILE")letopt_block_name=letdoc="Name of block device."inArg.(value&optvm_cName.root&info["name"]~doc)letopt_block_size=letdoc="Block storage to allow in MB"inArg.(value&opt(someint)None&info["size"]~doc)letmem=letdoc="Memory to allow in MB"inArg.(value&optint512&info["mem"]~doc)letbridge=letdoc="Bridges to allow"inArg.(value&opt_allstring[]&info["bridge"]~doc)letcpu=letdoc="CPUid to use"inArg.(value&optint0&info["cpu"]~doc)letvm_mem=letdoc="Assigned memory in MB"inArg.(value&optint32&info["mem"]~doc)letargs=letdoc="Boot arguments"inArg.(value&opt_allstring[]&info["arg"]~doc)letcolon_separated_c=letparses=matchString.split_on_char':'swith|[a;b]->Ok(a,Someb)|[_]->Ok(s,None)|_->Error(`Msg"format is 'name' or 'name:device-name'")andppppf(a,b)=Fmt.pfppf"%s:%s"a(matchbwithNone->a|Someb->b)inArg.conv(parse,pp)letblock=letdoc="Block device name (block or name:block-device-name)"inArg.(value&opt_allcolon_separated_c[]&info["block"]~doc)letnet=letdoc="Network device names (bridge or name:bridge)"inArg.(value&opt_allcolon_separated_c[]&info["net"]~doc)letrestart_on_fail=letdoc="Restart on fail"inArg.(value&flag&info["restart-on-fail"]~doc)letexit_code=letdoc="Exit code to restart on"inArg.(value&opt_allint[]&info["exit-code"]~doc)lettimestamp_c=letparses=matchPtime.of_rfc3339swith|Ok(t,_,_)->Okt|Error_->(* let's try to add T00:00:00-00:00 *)matchPtime.of_rfc3339(s^"T00:00:00-00:00")with|Ok(t,_,_)->Okt|Error_->Error(`Msg"couldn't parse timestamp")inArg.conv(parse,Ptime.pp_rfc3339())letsince=letdoc="Receive data since a specified timestamp (RFC 3339 encoded)"inArg.(value&opt(sometimestamp_c)None&info["since"]~doc)letcount=letdoc="Receive N data records"inArg.(value&optint20&info["count"]~doc)letsince_countsincecount=matchsincewith|None->`Countcount|Somesince->`Sincesinceletversion=Fmt.str"version v1.4.3 protocol version %a"Vmm_commands.pp_versionVmm_commands.currentletdefault_tmpdir=matchLazy.forceVmm_unix.unamewith|FreeBSD->"/var/run/albatross"|Linux->"/run/albatross"lettmpdir=letdoc="Albatross temporary directory"inArg.(value&optdirdefault_tmpdir&info["tmpdir"]~doc)letset_tmpdirpath=matchFpath.of_stringpathwith|Okpath->Vmm_core.set_tmpdirpath|Error`Msgm->invalid_argmletdefault_dbdir=matchLazy.forceVmm_unix.unamewith|Vmm_unix.FreeBSD->"/var/db/albatross"|Linux->"/var/lib/albatross"letdbdir=letdoc="Albatross database directory"inArg.(value&optdirdefault_dbdir&info["dbdir"]~doc)letset_dbdirpath=matchFpath.of_stringpathwith|Okpath->Vmm_unix.set_dbdirpath|Error`Msgm->invalid_argmletenable_stats=letdoc="Connect to albatross-stats to report statistics"inArg.(value&flag&info["enable-stats"]~doc)letretry_connections=letdoc="Number of retries when connecting to other daemons (log, console, stats etc). 0 aborts after one failure, -1 is unlimited retries."inArg.(value&optint2&info["retry-connections"]~doc)letsystemd_socket_activation=matchLazy.forceVmm_unix.unamewith|FreeBSD->Term.constfalse|Linux->letdoc="Pass this flag when systemd socket activation is being used"inArg.(value&flag&info["systemd-socket-activation"]~doc)letpub_key_type=letdoc="Asymmetric key type to use"inArg.(value&opt(Arg.enumX509.Key_type.strings)`ED25519&info["key-type"]~doc)letkey_bits=letdoc="Public key bits to use (only relevant for RSA)"inArg.(value&optint4096&info["bits"]~doc)letexit_status=function|Ok()->OkSuccess|Errore->Oke(* exit status already in use:
- 0 success
- 2 OCaml exception
- 123 "some error"
- 124 "cli error"
- 125 "internal error"
- 126 (bash) command invoked cannot execute
- 127 (bash) command not found
- 255 OCaml abort
*)letremote_command_failed=117lethttp_failed=118letlocal_authentication_failed=119letremote_authentication_failed=120letcommunication_failed=121letconnect_failed=122letexit_status_to_int=function|Success->Cmd.Exit.ok|Local_authentication_failed->local_authentication_failed|Remote_authentication_failed->remote_authentication_failed|Communication_failed->communication_failed|Connect_failed->connect_failed|Remote_command_failed->remote_command_failed|Cli_failed->Cmd.Exit.cli_error|Internal_error->Cmd.Exit.internal_error|Http_error->http_failedletexit_status_of_result=function|Ok(`Help|`Version)->Cmd.Exit.ok|Ok`Oka->exit_status_to_inta|Error`Term->Cmd.Exit.cli_error|Error`Parse->Cmd.Exit.cli_error|Error`Exn->Cmd.Exit.internal_errorletexits=Cmd.Exit.info~doc:"on communication (read or write) failure"communication_failed::Cmd.Exit.info~doc:"on connection failure"connect_failed::Cmd.Exit.info~doc:"on remote command execution failure"remote_command_failed::Cmd.Exit.info~doc:"on HTTP interaction failure"http_failed::Cmd.Exit.defaultsletauth_exits=[Cmd.Exit.info~doc:"on local authentication failure \
(certificate not accepted by remote)"local_authentication_failed;Cmd.Exit.info~doc:"on remote authentication failure \
(couldn't validate trust anchor)"remote_authentication_failed]