-
Notifications
You must be signed in to change notification settings - Fork 296
Expand file tree
/
Copy pathcli_operations.ml
More file actions
8327 lines (7837 loc) · 279 KB
/
cli_operations.ml
File metadata and controls
8327 lines (7837 loc) · 279 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(*
* Copyright (C) 2006-2009 Citrix Systems Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; version 2.1 only. with the special
* exception on linking described in file LICENSE.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*)
(**
* @group Command-Line Interface (CLI)
*)
open Cli_protocol
open Cli_util
open Cli_cmdtable
module Date = Clock.Date
module Listext = Xapi_stdext_std.Listext.List
module Unixext = Xapi_stdext_unix.Unixext
module D = Debug.Make (struct let name = __MODULE__ end)
open D
open Records
let failwith str = raise (Cli_util.Cli_failure str)
let failwithfmt fmt = Printf.ksprintf failwith fmt
exception ExitWithError of int
let bool_of_string param string =
try Record_util.bool_of_string string
with Record_util.Record_failure msg ->
let msg = Printf.sprintf "Failed to parse parameter '%s': %s" param msg in
raise (Record_util.Record_failure msg)
let get_bool_param params ?(default = false) param =
List.assoc_opt param params
|> Option.map (bool_of_string param)
|> Option.value ~default
let get_float_param params param ~default =
List.assoc_opt param params
|> Fun.flip Option.bind float_of_string_opt
|> Option.value ~default
let get_param params param ~default =
Option.value ~default (List.assoc_opt param params)
let get_set_param params ?(default = []) param =
List.assoc_opt param params
|> Option.map (String.split_on_char ',')
|> Option.value ~default
let get_map_param params ?(default = []) param =
let get_map x =
String.split_on_char ',' x
|> List.filter_map (fun x ->
match String.split_on_char ':' x with
| [k; v] ->
Some (k, v)
| _ ->
None
)
in
List.assoc_opt param params |> Option.map get_map |> Option.value ~default
(** [get_unique_param param params] is intended to replace [List.assoc_opt] in
the cases where a parameter can only exist once, as repeating it might
force the CLI to make choices the user didn't foresee. In those cases
raises an exception to warn the user to input it only once *)
let get_unique_param param params =
match List.find_all (fun (n, _) -> n = param) params with
| [] ->
None
| [(_, value)] ->
Some value
| _ :: _ :: _ ->
failwith
(Printf.sprintf
"Parameter %s is defined multiple times, define it only once." param
)
let no_force_msg =
"This operation is dangerous and may cause data loss. This operation must be \
forced (use --force)."
let fail_without_force params =
if not (get_bool_param params "force") then failwith no_force_msg
open Client
let progress_bar printer task_record =
let progress = task_record.API.task_progress in
let hashes = String.make (int_of_float (progress *. 70.)) '#' in
let animation = "|/-\\" in
let char =
animation.[int_of_float (progress *. 100.) mod String.length animation]
in
let line =
Printf.sprintf "\r %3d%% %c %s"
(int_of_float (progress *. 100.))
char hashes
in
Cli_printer.PStderr line |> printer
let wait_with_progress_bar printer rpc session_id task =
Cli_util.track (progress_bar printer) rpc session_id task ;
Cli_printer.PStderr "\n" |> printer ;
Cli_util.result_from_task rpc session_id task
let wait _printer rpc session_id task =
Cli_util.track (fun _ -> ()) rpc session_id task ;
Cli_util.result_from_task rpc session_id task
let waiter printer rpc session_id params task =
finally
(fun () ->
( if List.mem_assoc "progress" params then
wait_with_progress_bar
else
wait
)
printer rpc session_id task
)
(fun () -> Client.Task.destroy ~rpc ~session_id ~self:task)
(* Return the list of k=v pairs for maps.
Works for key which is not follow by a ':',
also match old syntax 'device-config-key' for backwards compatability *)
let read_map_params name params =
let len = String.length name + 1 in
(* include ':' *)
let filter_params =
List.filter
(fun (p, _) ->
Astring.String.is_prefix ~affix:name p && String.length p > len
)
params
in
List.map
(fun (k, v) -> (String.sub k len (String.length k - len), v))
filter_params
let read_set_params name params = List.map fst (read_map_params name params)
let get_chunks fd =
let buffer = Buffer.create 10240 in
let rec f bytes_read =
match unmarshal fd with
| Blob (Chunk len) ->
debug "Reading a chunk of %ld bytes" len ;
let bytes_read = bytes_read + Int32.to_int len in
if bytes_read > Constants.max_cli_upload_bytes then
failwith
(Printf.sprintf
"Fatal error: A CLI client tried to transfer more than the \
maximum allowed of %dB, aborting."
Constants.max_cli_upload_bytes
) ;
let data = Unixext.really_read_string fd (Int32.to_int len) in
Buffer.add_string buffer data ;
f bytes_read
| Blob End ->
Buffer.contents buffer
| _ ->
failwith "Thin CLI protocol error"
in
f 0
let get_client_file fd filename =
marshal fd (Command (Load filename)) ;
match unmarshal fd with
| Response OK ->
Some (get_chunks fd)
| Response Failed ->
None
| _ ->
failwith "Thin CLI protocol error"
let fail fd desc =
marshal fd (Command (PrintStderr (Printf.sprintf "Failed to read %s\n" desc))) ;
raise (ExitWithError 1)
let get_file_or_fail fd desc filename =
match get_client_file fd filename with
| None ->
fail fd desc
| Some chunks ->
chunks
let diagnostic_timing_stats printer rpc session_id params =
let counts = get_bool_param params "counts" in
let sort = List.sort (fun (x, _) (y, _) -> String.compare x y) in
let table_of_host host =
[
("host-uuid", Client.Host.get_uuid ~rpc ~session_id ~self:host)
; ("host-name-label", Client.Host.get_name_label ~rpc ~session_id ~self:host)
]
@
try Client.Host.get_diagnostic_timing_stats ~rpc ~session_id ~host ~counts
with e -> [("Error", Api_errors.to_string e)]
in
let all = List.map table_of_host (Client.Host.get_all ~rpc ~session_id) in
let sorted = List.map sort all in
printer (Cli_printer.PTable sorted)
let get_hosts_by_name_or_id rpc session_id name =
let hosts = Client.Host.get_all_records_where ~rpc ~session_id ~expr:"true" in
let allrecs =
List.map
(fun (host, host_r) ->
let r = host_record rpc session_id host in
r.setrefrec (host, host_r) ;
r
)
hosts
in
let hosts =
List.filter
(fun x ->
safe_get_field (field_lookup x.fields "name-label") = name
|| safe_get_field (field_lookup x.fields "uuid") = name
)
allrecs
in
hosts
let get_host_by_name_or_id rpc session_id name =
let hosts = get_hosts_by_name_or_id rpc session_id name in
if hosts = [] then failwith ("Host " ^ name ^ " not found") ;
List.nth hosts 0
let get_host_from_session rpc session_id =
Client.Session.get_this_host ~rpc ~session_id ~self:session_id
(* Create a VBD record in database and attempt to hotplug it, ignoring hotplug errors *)
let create_vbd_and_plug_with_other_config rpc session_id vm vdi device_name
bootable rw cd unpluggable qtype qparams other_config =
let vbd =
Client.VBD.create ~rpc ~session_id ~vM:vm ~vDI:vdi ~userdevice:device_name
~bootable ~mode:rw ~_type:cd ~unpluggable ~empty:false
~qos_algorithm_type:qtype ~qos_algorithm_params:qparams ~other_config
~device:"" ~currently_attached:false
in
try Client.VBD.plug ~rpc ~session_id ~self:vbd
with Api_errors.Server_error (_, _) as e ->
debug "VBD created but not hotplugged: %s" (Api_errors.to_string e)
let create_vbd_and_plug rpc session_id vm vdi device_name bootable rw cd
unpluggable qtype qparams =
create_vbd_and_plug_with_other_config rpc session_id vm vdi device_name
bootable rw cd unpluggable qtype qparams []
let create_owner_vbd_and_plug rpc session_id vm vdi device_name bootable rw cd
unpluggable qtype qparams =
create_vbd_and_plug_with_other_config rpc session_id vm vdi device_name
bootable rw cd unpluggable qtype qparams
[(Constants.owner_key, "")]
(* ---------------------------------------------------------------------
CLI Operation Implementation
--------------------------------------------------------------------- *)
let user_password_change _ rpc session_id params =
let old_pwd = Listext.assoc_default "old" params ""
(* "new" must be in params here, since it is a required parameter. *)
and new_pwd = List.assoc "new" params in
Client.Session.change_password ~rpc ~session_id ~old_pwd ~new_pwd
(** Low level CLI interface **)
(* Have a record for each class of the API, then accessor functions for it in a consistent way *)
(* e.g. vm-create vm-destroy vm-param-list vm-param-set vm-param-get vm-param-add vm-param-remove *)
(* create creates an instance of a class with a minimal set of required fields set on the cmd line *)
(* and the rest as optional params - e.g. *)
(* xe vm-create name-label=mynewvm name-description="a nice new vm" *)
(* and returns the uuid of the created object *)
(* vm-destroy takes the uuid and destroys the object *)
(* vm-param-list takes the uuid and lists either a default set of parameters, or those passed *)
let alltrue l = List.fold_left ( && ) true l
let safe_get_field x =
try x.get () with
| Api_errors.Server_error (s, _) as e ->
if s = Api_errors.handle_invalid then
"<invalid reference>"
else
raise e
| e ->
raise e
type fieldtype = Normal | Set of string | Map of string
let get_field_type fieldname record =
if List.exists (fun field -> field.name = fieldname) record then
Normal
else if
(* New 'normal' behaviour is to split map name from key by the separator ':' *)
String.contains fieldname ':'
then
let i = String.index fieldname ':' in
let real_fieldname = String.sub fieldname 0 i in
try
let field = List.find (fun field -> field.name = real_fieldname) record in
if field.get_set <> None then
Set field.name
else if field.get_map <> None then
Map field.name
else
failwith ("Field '" ^ field.name ^ "' is not a set or map")
with Not_found -> failwith ("Unknown field '" ^ fieldname ^ "'")
else
(* Old behaviour is to match like this: param-name-key=value *)
(* Find all the maps, then sort in length order, longest first *)
let mapfields = List.filter (fun field -> field.get_map <> None) record in
let mapfields =
List.sort
(fun a b -> compare (String.length b.name) (String.length a.name))
mapfields
in
try
(* Find the first (longest) matching field *)
let field =
List.find
(fun field ->
Astring.String.is_prefix ~affix:(field.name ^ "-") fieldname
)
mapfields
in
Map field.name
with Not_found -> (
let setfields = List.filter (fun field -> field.get_set <> None) record in
let setfields =
List.sort
(fun a b -> compare (String.length b.name) (String.length a.name))
setfields
in
try
let field =
List.find
(fun field ->
Astring.String.is_prefix ~affix:(field.name ^ "-") fieldname
)
setfields
in
Set field.name
with _ -> failwith ("Unknown field '" ^ fieldname ^ "'")
)
let filter_records_on_set_param records (k, v) s =
(* On entry here, s is the name of the parameter, and k will be of the form s[:-]contains *)
let n = String.length s in
let contains = String.sub k (n + 1) (String.length k - n - 1) in
if contains <> "contains" then
failwith
"Invalid syntax for set filtering (should be set-param:contains=key)" ;
let filterfn record =
let field = field_lookup record.fields s in
let get_set =
match field.get_set with
| Some x ->
x
| None ->
failwith (Printf.sprintf "Records broken (field %s)" s)
in
try
let set = get_set () in
let set, v =
if field.case_insensitive then
(List.map String.lowercase_ascii set, String.lowercase_ascii v)
else
(set, v)
in
List.exists (fun member -> v = member) set
with _ -> false
in
List.filter filterfn records
let filter_records_on_map_param records (k, v) s =
(* On entry here, s is the name of the parameter, and k will be of the form s[:-]key *)
let n = String.length s in
let key = String.sub k (n + 1) (String.length k - n - 1) in
let filterfn record =
let field = field_lookup record.fields s in
let get_map =
match field.get_map with
| Some x ->
x
| None ->
failwith (Printf.sprintf "Records broken (field %s)" s)
in
try
let map = get_map () in
let map, key, v =
if field.case_insensitive then
( List.map (fun (k, v) -> (String.lowercase_ascii k, v)) map
, String.lowercase_ascii key
, String.lowercase_ascii v
)
else
(map, key, v)
in
List.mem_assoc key map && List.assoc key map = v
with _ -> false
in
List.filter filterfn records
let filter_records_on_normal_param records (k, v) =
let filterfn record =
let field = field_lookup record.fields k in
let value = safe_get_field field in
if field.case_insensitive then
String.lowercase_ascii value = String.lowercase_ascii v
else
value = v
in
List.filter filterfn records
let filter_records_on_fields records (k, v) =
(* Ignore empty lists *)
if records = [] then
[]
else
(* We can only tell what types fields are by looking at a record itself. *)
(* We use the first one *)
let firstrec = List.hd records in
(* Switch on the type of the field *)
match get_field_type k firstrec.fields with
| Normal ->
filter_records_on_normal_param records (k, v)
| Map s ->
filter_records_on_map_param records (k, v) s
| Set s ->
filter_records_on_set_param records (k, v) s
let stdparams =
[
"server"
; "password"
; "port"
; "username"
; "minimal"
; "force"
; "multiple"
; "all"
; "message-priority"
; "trace"
]
(* This goes through the list of parameters, extracting any of the form map-name-key=value *)
(* where map-name is the name of a map in the class. These will be used to set the key-value *)
(* pair in the map. Returns a list of params that didn't fit this form *)
let choose_params params defaults =
if List.mem_assoc "params" params then
let ps = List.assoc "params" params in
if ps = "all" then
[]
else
Astring.String.cuts ~sep:"," ps
else
defaults
let select_fields params records default_params =
let params = choose_params params default_params in
if params = [] then
List.map (fun record -> record.fields) records
else
List.map
(fun record ->
List.filter (fun field -> List.mem field.name params) record.fields
)
records
let print_field x =
let append =
if x.get_set <> None then (* Set *)
if x.add_to_set = None then
" (SRO)"
else
" (SRW)"
else if x.get_map <> None then (* map *)
if x.add_to_map = None then
" (MRO)"
else
" (MRW)"
else if x.set = None then
" ( RO)"
else
" ( RW)"
in
let result = safe_get_field x in
( (x.name
^ append
^
if x.deprecated then
" [DEPRECATED]"
else
""
)
, result
)
type printer = Cli_printer.print_fn
type rpc = Rpc.call -> Rpc.response
type params = (string * string) list
(* Check the params for "database:vdi-uuid=" - if this parameter is present, *)
(* open the database on the specified VDI and use the resulting session_id. *)
(* If the parameter is not present, use the original session_id. *)
let with_specified_database rpc session_id params f =
let database_params = read_map_params "database" params in
let use_db_vdi = List.mem_assoc "vdi-uuid" database_params in
let use_db_file = List.mem_assoc "filename" database_params in
if use_db_vdi && use_db_file then
failwith "xapi can query a DB vdi or a DB file, but not both." ;
let session_id =
if use_db_vdi then
let database_vdi_uuid = List.assoc "vdi-uuid" database_params in
let database_vdi =
Client.VDI.get_by_uuid ~rpc ~session_id ~uuid:database_vdi_uuid
in
Client.VDI.open_database ~rpc ~session_id ~self:database_vdi
else if use_db_file then
let database_file = List.assoc "filename" database_params in
Client.Session.create_from_db_file ~rpc ~session_id
~filename:database_file
else
session_id
in
finally
(fun () -> f session_id)
(fun () ->
if use_db_vdi || use_db_file then Client.Session.logout ~rpc ~session_id
)
let make_param_funs getallrecs getbyuuid record class_name def_filters
def_list_params rpc session_id =
let get_record2 rpc session_id x =
let r = record rpc session_id x in
r.fields
in
let get_record rpc session_id uuid =
get_record2 rpc session_id (getbyuuid ~rpc ~session_id ~uuid)
in
let list printer rpc session_id params : unit =
with_specified_database rpc session_id params (fun session_id ->
let all = getallrecs ~rpc ~session_id ~expr:"true" in
let all_recs =
List.map
(fun (r, x) ->
let record = record rpc session_id r in
record.setrefrec (r, x) ;
record
)
all
in
(* Filter on everything on the cmd line except params=... *)
let filter_params =
List.filter
(fun (p, _) -> not (List.mem p ("params" :: stdparams)))
params
in
(* Filter out all params beginning with "database:" *)
let filter_params =
List.filter
(fun (p, _) -> not (Astring.String.is_prefix ~affix:"database:" p))
filter_params
in
(* Add in the default filters *)
let filter_params = def_filters @ filter_params in
(* Filter all the records *)
let records =
List.fold_left filter_records_on_fields all_recs filter_params
in
let print_all = get_bool_param params "all" in
let print_params =
select_fields params
( if print_all then
all_recs
else
records
)
def_list_params
in
let print_params =
List.map
(fun fields -> List.filter (fun field -> not field.hidden) fields)
print_params
in
let print_params =
List.map
(fun fields ->
List.map
(fun field ->
if field.expensive then
makeexpensivefield field
else
field
)
fields
)
print_params
in
printer
(Cli_printer.PTable (List.map (List.map print_field) print_params))
)
in
let p_list printer rpc session_id params : unit =
with_specified_database rpc session_id params (fun session_id ->
let record = get_record rpc session_id (List.assoc "uuid" params) in
let record = List.filter (fun field -> not field.hidden) record in
printer (Cli_printer.PTable [List.map print_field record])
)
in
let p_get printer rpc session_id params : unit =
with_specified_database rpc session_id params (fun session_id ->
let record = get_record rpc session_id (List.assoc "uuid" params) in
let param = List.assoc "param-name" params in
let x = field_lookup record param in
let std () = printer (Cli_printer.PList [safe_get_field x]) in
if List.mem_assoc "param-key" params then
let key = List.assoc "param-key" params in
match x.get_map with
| Some f ->
let result =
try List.assoc key (f ())
with _ ->
failwith (Printf.sprintf "Key %s not found in map" key)
in
printer (Cli_printer.PList [result])
| None ->
std ()
else
std ()
)
in
let p_set (_ : printer) rpc session_id params =
let record = get_record rpc session_id (List.assoc "uuid" params) in
let set_params =
List.filter (fun (p, _) -> not (List.mem p ("uuid" :: stdparams))) params
in
(* Hashtable set_map_table contains key as set_map function
and associated value as list of (key, value) pairs to set a map field *)
let set_map_table :
((string * string) list -> unit, (string * string) list) Hashtbl.t =
Hashtbl.create 10
in
let set_field (k, v) =
let field_type = get_field_type k record in
match field_type with
| Map s -> (
let field = field_lookup record s in
let n = String.length s in
let key = String.sub k (n + 1) (String.length k - n - 1) in
let get_map =
match field.get_map with
| Some x ->
x
| None ->
failwith (Printf.sprintf "Broken Records (field %s)" s)
in
(* If set_in_map is present, use it instead of using remove_from_map followed by add_to_map. *)
(* If set_map is present then accumulate all (key, value) pairs into set_map_table *)
match (field.set_in_map, field.set_map) with
| Some set_in_map, None ->
set_in_map key v
| None, Some set_map ->
let existing_params =
Option.value
(Hashtbl.find_opt set_map_table set_map)
~default:[]
in
Hashtbl.replace set_map_table set_map ((key, v) :: existing_params)
| None, None ->
let add_to_map =
match field.add_to_map with
| Some f ->
f
| None ->
failwith ("Map field '" ^ s ^ "' is read-only.")
in
let remove_from_map =
match field.remove_from_map with
| Some f ->
f
| None ->
failwith (Printf.sprintf "Records broken (field %s)" s)
in
let map = get_map () in
if List.mem_assoc key map then remove_from_map key ;
add_to_map key v
| Some _, Some _ ->
failwith (Printf.sprintf "Broken Records (field %s)" s)
)
| Set _ ->
failwith "Cannot param-set on set fields"
| Normal -> (
let field = field_lookup record k in
let set =
match (field.set, field.add_to_map) with
| Some f, _ ->
f
| None, Some _ ->
failwith
("Field '"
^ k
^ "' is a map or set. use the 'name:key=value' syntax."
)
| None, None ->
failwith ("Field '" ^ k ^ "' is read-only.")
in
try set v with
| Failure e when e = "int_of_string" ->
failwith ("Parameter " ^ k ^ " must be an integer")
| Failure e when e = "float_of_string" ->
failwith ("Parameter " ^ k ^ " must be a floating-point number")
| Invalid_argument e when e = "bool_of_string" ->
failwith ("Parameter " ^ k ^ " must be a boolean (true or false)")
| e ->
raise e
)
in
List.iter set_field set_params ;
Hashtbl.iter (fun func params -> func params) set_map_table
in
let p_add (_ : printer) rpc session_id params =
let record = get_record rpc session_id (List.assoc "uuid" params) in
let param_name = List.assoc "param-name" params in
let filter_params =
List.filter
(fun (p, _) ->
not (List.mem p ("uuid" :: "param-name" :: "param-key" :: stdparams))
)
params
in
match field_lookup record param_name with
| {add_to_set= Some f; _} ->
if List.mem_assoc "param-key" params then
let key = List.assoc "param-key" params in
f key
else
failwith
"When adding a key to a set, use the syntax: *-param-add \
param-name=<name> param-key=<key>"
| {add_to_map= Some f; _} ->
List.iter (fun (k, x) -> f k x) filter_params
| {get_set= Some _; add_to_set= None; _}
| {get_map= Some _; add_to_map= None; _} ->
failwith "Parameter is read-only"
| _ ->
failwith "Can only add to parameters of type Set or Map"
in
let p_remove (_ : printer) rpc session_id params =
let record = get_record rpc session_id (List.assoc "uuid" params) in
let param_name = List.assoc "param-name" params in
let param_key = List.assoc "param-key" params in
match field_lookup record param_name with
| {get_set= Some g; remove_from_set= Some f; _} ->
if List.mem param_key (g ()) then
f param_key
else
failwith (Printf.sprintf "Key %s is not in the set" param_key)
| {get_map= Some g; remove_from_map= Some f; _} ->
if List.mem_assoc param_key (g ()) then
f param_key
else
failwith (Printf.sprintf "Key %s is not in map" param_key)
| {get_set= Some _; remove_from_set= None; _}
| {get_map= Some _; remove_from_map= None; _} ->
failwith "Cannot remove parameters from read-only map"
| _ ->
failwith "Can only remove from parameters of type Set or Map"
in
let p_clear (_ : printer) rpc session_id params =
let record = get_record rpc session_id (List.assoc "uuid" params) in
let param_name = List.assoc "param-name" params in
match field_lookup record param_name with
| {get_set= Some f; remove_from_set= Some g; _} ->
List.iter g (f ())
| {clear_map= Some g; _} ->
g ()
| {set_map= Some g; _} ->
g []
| {get_map= Some f; remove_from_map= Some g; _} ->
List.iter g (List.map fst (f ()))
| {set= Some f; _} -> (
try f "" with _ -> failwith "Cannot clear this parameter"
)
| _ ->
failwith "Can only clear RW parameters"
in
let gen_frontend (rpc : rpc) (session_id : API.ref_session) =
let make_cmdtable_data (opname, reqd, optn, help, impl, std) =
( opname
, {
reqd
; optn
; help
; implementation= No_fd impl
; flags=
( if std then
[Standard]
else
[]
)
}
)
in
try
let all =
List.filter
(fun x -> not x.hidden)
(record rpc session_id Ref.null).fields
in
let all_optn = List.map (fun r -> r.name) all in
let settable =
List.map (fun r -> r.name) (List.filter (fun r -> r.set <> None) all)
in
let settable =
settable
@ List.map
(fun r -> r.name ^ ":")
(List.filter
(fun r ->
r.add_to_map <> None
|| r.set_in_map <> None
|| r.set_map <> None
)
all
)
in
let addable =
List.map
(fun r -> r.name)
(List.filter
(fun r -> r.add_to_set <> None || r.add_to_map <> None)
all
)
in
let clearable =
List.map
(fun r -> r.name)
(List.filter
(fun r -> r.set <> None || r.get_set <> None || r.get_map <> None)
all
)
in
(* We need the names of the set and map filters *)
let sm_param_names =
let sets = List.filter (fun field -> field.get_set <> None) all in
List.map (fun field -> field.name ^ ":contains") sets
in
let cli_name n = class_name ^ "-" ^ n in
let plural =
if class_name = "patch" then
"patches"
else
class_name ^ "s"
in
let ops =
[
( cli_name "list"
, []
, ("params" :: "database:" :: all_optn) @ sm_param_names
, "Lists all the "
^ plural
^ ", filtering on the optional arguments. To filter on map \
parameters, use the syntax 'map-param:key=value'"
, list
, class_name = "vm" || class_name = "network" || class_name = "sr"
)
; ( cli_name "param-list"
, ["uuid"]
, ["database:"]
, "Lists all the parameters of the object specified by the uuid."
, p_list
, false
)
; ( cli_name "param-get"
, ["uuid"; "param-name"]
, ["param-key"; "database:"]
, "Gets the parameter specified of the object. If the parameter is a \
map of key=value pairs, use 'param-key=<key>' to get the value \
associated with a particular key."
, p_get
, false
)
]
in
let ops =
if settable <> [] then
( cli_name "param-set"
, ["uuid"]
, settable
, "Sets the parameter specified. If param-value is not given, the \
parameter is set to a null value. To set a (key,value) pair in a \
map parameter, use the syntax 'map-param:key=value'."
, p_set
, false
)
:: ops
else
ops
in
let ops =
if addable <> [] then
ops
@ [
( cli_name "param-add"
, ["uuid"; "param-name"]
, ["param-key"]
, "Adds to a set or map parameter. If the parameter is a set, \
use param-key=<key to add>. If the parameter is a map, pass \
the values to add as 'key=value' pairs."
, p_add
, false
)
; ( cli_name "param-remove"
, ["uuid"; "param-name"; "param-key"]
, []
, "Removes a member or a key,value pair from a set/map \
respectively."
, p_remove
, false
)
]
else
ops
in
let ops =
if clearable <> [] then
ops
@ [
( cli_name "param-clear"
, ["uuid"; "param-name"]
, []
, "Clears the specified parameter (param-name can be "
^ String.concat "," clearable
^ ")."
, p_clear
, false
)
]
else
ops
in
List.map make_cmdtable_data ops
with _ -> []
in
gen_frontend rpc session_id
(* the fields to show when doing `xe <class>-list`, whereas
`xe <class>-param-list uuid=...` shows all the non-hidden fields of a record *)
let gen_cmds rpc session_id =
let mk = make_param_funs in
List.concat
[
Client.Pool.(
mk get_all_records_where get_by_uuid pool_record "pool" []
["uuid"; "name-label"; "name-description"; "master"; "default-SR"]
rpc session_id
)
; Client.PIF.(
mk get_all_records_where get_by_uuid pif_record "pif" []
[
"uuid"
; "device"
; "VLAN"
; "MAC"
; "network-uuid"
; "currently-attached"
; "host-uuid"
]
rpc session_id
)
; Client.Bond.(
mk get_all_records_where get_by_uuid bond_record "bond" []
["uuid"; "master"; "slaves"]
rpc session_id
)
; Client.VLAN.(
mk get_all_records_where get_by_uuid vlan_record "vlan" []
["uuid"; "tagged-PIF"; "untagged-PIF"; "tag"]
rpc session_id
)
; Client.Tunnel.(
mk get_all_records_where get_by_uuid tunnel_record "tunnel" []
["uuid"; "transport-PIF"; "access-PIF"; "status"]
rpc session_id
)