This patch replaces restrictions.ml by features.ml and editions.ml, and splits up license.ml in three separate files (license.ml, license_init.ml and license_file.ml).
The Features module controls which XCP features are enabled. The Editions module defines "editions", which are sets of features that are enabled. An XCP host is always running as (exactly) one particular edition. Currently, there is just one edition defined, called "Free" (for backwards compatibility), in which all currently implemented features are enabled.
One use of this is that new or experimental features can be enabled only in special editions to keep them separate from the stable version of XCP.
Signed-off-by: Rob Hoes <rob.hoes@citrix.com>
let to_features = function
| Free -> all_features
-let edition_to_int = function
- | Free | _ -> 0
-
-let equal e0 e1 =
- edition_to_int e0 = edition_to_int e1
-
-let min l =
- Free
-
type edition =
| Free (** Default Edition *)
-(** Raised by {!of_string} if the given string does not map to an edition. *)
+(** Raised by {!edition_of_string} if the given string does not map to an edition. *)
exception Undefined_edition of string
(** Convert a string to an {!edition}. *)
(** Get the list of {!feature}s enabled for a given {!edition}. *)
val to_features : edition -> Features.feature list
-(** Compare two editions for equality (used before pool join). *)
-val equal : edition -> edition -> bool
-
-(** Return the "least capable" edition (used to determine the pool edition). *)
-val min : edition list -> edition
-
type feature = VLAN | QoS | Shared_storage | Netapp | Equalogic | Pooling
| HA | Marathon | Email | Performance | WLB | RBAC | DMC | Checkpoint
- | Vswitch_controller | CPU_masking | Connection | No_platform_filter | No_nag_dialog | VMPR
+ | Vswitch_controller | CPU_masking | Connection | No_platform_filter | No_nag_dialog
type orientation = Positive | Negative
Connection, ("restrict_connection", Negative, "Cnx");
No_platform_filter, ("platform_filter", Negative, "Plat");
No_nag_dialog, ("regular_nag_dialog", Negative, "nonag");
- VMPR, ("restrict_vmpr", Negative, "VMPR");
]
let string_of_feature f =
| Connection (** Used by XenCenter *)
| No_platform_filter (** Filter platform data *)
| No_nag_dialog (** Used by XenCenter *)
-| VMPR (** Enable use of VM Protection and Recovery *)
(** The list of all known features. *)
val all_features : feature list
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*)
-(* OLD:
- * There are 2 ways that a license can be registered by xapi. The
- * first is on startup, when the file /etc/xensource/license is read,
- * and the second is the api call 'host.license_apply'. There are also
- * 2 possible problems with a license - that it might have expired, and
- * that it might be invalid. The API call will refuse to do anything if
- * the license has either problem, but initial startup will always
- * apply a parsable license, even if it has expired.
- *
- * The license is checked to see if it has expired in exactly one place
- * - Vmops.create_check, and so an expired license will mean it is
- * impossible to start new domains. *)
open Stringext
open Pervasiveext
module D = Debug.Debugger(struct let name="license" end)
open D
-(* Set from config file: *)
-let filename = ref ""
-
(* Defaults *)
-let sku = "XE Enterprise"
+let edition = Edition.Free
(* Round a date, given by Unix.time, to days *)
let round_to_days d =
let default_sockets = 1
let default_productcode = ""
-let sku_and_name_of_edition = function
-| _ -> "undefined", "Undefined"
-
-(* Convert the sku_type encoded and signed in the license into a sku_marketing_name by looking up the
- key in an XML table stored in the dom0. *)
-let marketing_string_of_sku sku =
- try
- let xml = Unixext.read_whole_file_to_string Xapi_globs.sku_marketing_name_db in
- let db = Hashtbl_xml.of_xml (Xmlm.make_input (`String (0,xml))) in
- if Hashtbl.mem db sku
- then Hashtbl.find db sku
- else begin
- warn "marketing_string_of_sku %s: no corresponding entry in the sku_marketing_name db; defaulting to \"\"" sku;
- ""
- end
- with
- | Unix.Unix_error(Unix.ENOENT, _, _) -> warn "marketing_string_of_sku %s: sku_marketing_name db missing; defaulting to \"\"" sku; ""
- | Hashtbl_xml.Unmarshall_error x -> warn "marketing_string_of_sku %s: Caught error unmarshalling sku_marketing_name db: %s; defaulting to \"\"" sku x; ""
- | e -> warn "marketing_string_of_sku %s: Caught unknown exception unmarshalling sku_marketing_name db: %s; defaulting to \"\"" sku (Printexc.to_string e); ""
-
-
(* Only read out the fields we care about. The signature covers the other
fields so there's no verification required here. *)
type license =
let default () =
{
- sku = sku;
+ sku = Edition.to_string edition;
version = default_version;
serialnumber = "";
sockets = default_sockets;
state = "";
postalcode = "";
country = "";
- sku_marketing_name = marketing_string_of_sku sku;
+ sku_marketing_name = Edition.to_marketing_name edition;
}
let license : license ref = ref (default ())
-exception LicenseParseError
-exception LicenseCannotReadFile
-exception LicenseFieldMissing of string
-exception License_expired of license
-exception License_file_deprecated
-
(* Calls to obtain info about license *)
let check_expiry l =
Unix.time () < l.expiry
-let license_valid () = true
-
-(* License setting functions *)
-
-let validate_signature fname =
- Gpg.with_signed_cleartext fname
- (fun fingerprint fd ->
- (match fingerprint with
- | Some f ->
- (* base64-encoded fingerprint of our licensing public key *)
- if (Base64.encode f)<>"QzA5Qzk4REIwNjM4RjNFQjZEQUFERkU4QTJCRjA0QkM3QThDNzhBNw=="
- then
- (
- debug "Got fingerprint: %s" f;
- (* debug "Encoded: %s" (Base64.encode f); -- don't advertise the fact that we've got an encoded string in here! *)
- raise Gpg.InvalidSignature
- )
- | None ->
- debug "No fingerprint!";
- raise Gpg.InvalidSignature);
- Unixext.read_whole_file 500 500 fd)
-
-(* only activation keys are accepted as license files since XS 6.0 *)
-let parse_license license_data =
- let lic_xml = Xml.parse_string license_data in
-
- let readfld fname attrs =
- try
- List.assoc fname attrs
- with Not_found -> raise (LicenseFieldMissing fname) in
-
- let maybe_readfld fname attrs =
- try
- List.assoc fname attrs
- with Not_found -> "" in
+let license_valid () = check_expiry !license
- match lic_xml with
- | Xml.Element("xe_license", attrs, _) ->
- let sku = readfld "sku_type" attrs in
- (* we now only accept activation keys for the free edition fo XS *)
- if sku <> "XE Express" then
- raise License_file_deprecated
- else
- {sku = sku;
- version = readfld "version" attrs;
- serialnumber = readfld "serialnumber" attrs;
- sockets = int_of_string (readfld "sockets" attrs);
- productcode = (readfld "productcode" attrs);
- expiry = float_of_string (readfld "expiry" attrs);
- grace = "no";
- name = maybe_readfld "name" attrs;
- company = maybe_readfld "company" attrs;
- address1 = maybe_readfld "address1" attrs;
- address2 = maybe_readfld "address2" attrs;
- city = maybe_readfld "city" attrs;
- state = maybe_readfld "state" attrs;
- postalcode = maybe_readfld "postalcode" attrs;
- country = maybe_readfld "country" attrs;
- sku_marketing_name = marketing_string_of_sku sku;
- }
- | _ -> raise LicenseParseError
-
-(* only activation keys are accepted as license files since XS 6.0 *)
-let read_license_file fname =
- try
- Unix.access fname [Unix.F_OK];
- let license_data = validate_signature fname in
- let newlicense = parse_license license_data in
- Some newlicense
- with
- | License_file_deprecated -> raise License_file_deprecated
- | e ->
- begin
- debug "Failed to read license file: %s" (Printexc.to_string e);
- None
- end
-
-(* only activation keys are accepted as license files since XS 6.0 *)
-let do_parse_and_validate fname =
- try
- let _ = try Unix.access fname [Unix.F_OK] with _ -> raise LicenseCannotReadFile in
- let license_data = validate_signature fname in
- let newlicense = parse_license license_data in
-
- (if not (check_expiry newlicense) then raise (License_expired newlicense));
-
- (* At this point, license is valid and hasn't expired *)
- license := newlicense
- with e ->
- (match e with
- | License_expired l -> warn "License has expired"
- | LicenseCannotReadFile -> warn "License application failed: cannot read license file."
- | Gpg.InvalidSignature -> warn "License application failed: invalid signature on license file."
- | LicenseFieldMissing fname -> warn "License application failed: essential field '%s' missing from license." fname
- | LicenseParseError -> warn "License application failed: reverting to previous license"
- | License_file_deprecated -> warn "License application failed: deprecated license file"
- | e -> warn "License application failed: exception '%s' in license parsing." (Printexc.to_string e);
- log_backtrace ());
- raise e
-
-let write_grace_to_file grace_expiry =
- let grace_expiry_str = string_of_float grace_expiry in
- Unixext.write_string_to_file Xapi_globs.upgrade_grace_file grace_expiry_str
-
-let read_grace_from_file () =
- try
- let grace_expiry_str = Unixext.read_whole_file_to_string Xapi_globs.upgrade_grace_file in
- float_of_string grace_expiry_str
- with _ -> 0.
-
-(* xapi calls this function upon startup *)
-let initialise ~__context ~host =
- let existing_license_params = Db.Host.get_license_params ~__context ~self:host in
- let existing_edition = Db.Host.get_edition ~__context ~self:host in
- let default = default () in
- let new_license = try
- let existing_license = of_assoc_list existing_license_params in
- match existing_edition with
- | "free" ->
- (* old Floodgate-free behaviour *)
- begin try
- do_parse_and_validate !filename;
- info "Existing free license with expiry date %s still in effect." (Date.to_string (Date.of_float !license.expiry));
- !license (* do_parse_and_validate already sets !license *)
- with
- | License_expired l -> l (* keep expired license *)
- | _ ->
- (* activation file does not exist or is invalid *)
- if existing_license.expiry < default.expiry then begin
- info "Existing free license with expiry date %s still in effect." (Date.to_string (Date.of_float existing_license.expiry));
- {default with expiry = existing_license.expiry}
- end else begin
- info "Generating new free license, which needs to be activated in 30 days.";
- default
- end
- end
- | "enterprise" | "platinum" ->
- (* existing license is a v6 Essentials license -> try to check one out again *)
- begin try
- V6client.get_v6_license ~__context ~host ~edition:existing_edition;
- with _ -> error "The license-server connection details (address or port) were missing or incomplete." end;
- begin match !V6client.licensed with
- | None ->
- let upgrade_grace = read_grace_from_file () > Unix.time () in
- if upgrade_grace then begin
- info "No %s license is available, but we are still in the upgrade grace period." existing_edition;
- {existing_license with grace = "upgrade grace"}
- end else begin
- info "No %s license is available. Essentials features have been disabled." existing_edition;
- {existing_license with expiry = 0.} (* expiry date 0 means 01-01-1970, so always expired *)
- end
- | Some license ->
- info "Successfully checked out %s license." existing_edition;
- (* delete upgrade-grace file, if it exists *)
- Unixext.unlink_safe Xapi_globs.upgrade_grace_file;
- if !V6client.grace then begin
- Grace_retry.retry_periodically host existing_edition;
- {existing_license with grace = "regular grace"; expiry = !V6client.expires}
- end else
- {existing_license with grace = "no"; expiry = !V6client.expires}
- end
- | "" ->
- (* upgrade from pre-MNR *)
- if existing_license.sku = "XE Express" then begin
- info "Upgrade from free: set to free edition.";
- (* all existing license_params are kept; only fill in edition field *)
- Db.Host.set_edition ~__context ~self:host ~value:"free";
- {default with expiry = existing_license.expiry}
- end else begin
- info "Upgrade from Essentials: transition to enterprise edition (30-day grace license).";
- Db.Host.set_edition ~__context ~self:host ~value:"enterprise";
- let expiry = upgrade_grace_expiry () in
- write_grace_to_file expiry;
- Unixext.unlink_safe !filename;
- V6alert.send_v6_upgrade_grace_license ();
- let sku, name = sku_and_name_of_edition "enterprise" in
- {default with sku = sku; expiry = expiry; grace = "upgrade grace"; sku_marketing_name = name}
- end
- | _ ->
- warn "Edition field corrupted; generating a new free license, which needs to be activated in 30 days.";
- default
- with _ ->
- (* no license_params -> first boot *)
- Db.Host.set_edition ~__context ~self:host ~value:"free";
- begin try
- do_parse_and_validate !filename;
- info "Found a free-license activation key with expiry date %s." (Date.to_string (Date.of_float !license.expiry));
- !license (* do_parse_and_validate already sets !license *)
- with
- | License_expired l -> l (* keep expired license *)
- | _ ->
- (* activation file does not exist or is invalid *)
- info "Generating new free license, which needs to be activated in 30 days.";
- default
- end
- in
- license := new_license
-
(** Type for holding all details about a license *)
type license =
- {
- sku : string;
- (** License type: currently either [XE Express] (free) or [XE Enterprise] (paid for) *)
- version : string;
- (** No longer used *)
- serialnumber : string;
- (** No longer used *)
- sockets : int;
- (** No longer used *)
- productcode : string;
- (** No longer used *)
- expiry : float;
- (** Expiry date (result of Unix.time) *)
- grace : string;
- (** Indicates whether the current license is a grace license.
- * Possible values: "no", "upgrade grace", "regular grace" *)
+ {
+ sku : string; (** License type *)
+ version : string; (** No longer used *)
+ serialnumber : string; (** No longer used *)
+ sockets : int; (** No longer used *)
+ productcode : string; (** No longer used *)
+ expiry : float; (** Expiry date (result of Unix.time) *)
+ grace : string; (** Indicates whether the current license is a grace license.
+ * Possible values: "no", "upgrade grace", "regular grace" *)
- name : string;
- (** No longer used *)
- company : string;
- (** No longer used *)
- address1 : string;
- (** No longer used *)
- address2 : string;
- (** No longer used *)
- city : string;
- (** No longer used *)
- state : string;
- (** No longer used *)
- postalcode : string;
- (** No longer used *)
- country : string;
- (** No longer used *)
+ name : string; (** No longer used *)
+ company : string; (** No longer used *)
+ address1 : string; (** No longer used *)
+ address2 : string; (** No longer used *)
+ city : string; (** No longer used *)
+ state : string; (** No longer used *)
+ postalcode : string; (** No longer used *)
+ country : string; (** No longer used *)
- sku_marketing_name : string;
- (** Official marketing name of the license *)
- }
+ sku_marketing_name : string; (** Official marketing name of the license *)
+ }
+(** The current license on this host. *)
val license : license ref
-(** The current license *)
-val filename : string ref
-(** Path to the license file in use. Only free-license activation keys are
- * used since XS 6.0 *)
-
-exception Missing_license_param of string
-(** Thrown if we fail to find a license param *)
-exception LicenseParseError
-(** Thrown if the license data is malformed *)
-exception LicenseCannotReadFile
-(** Thrown if the given license file cannot be opened *)
-exception LicenseFieldMissing of string
-(** Thrown if a particular field is missing from the license data *)
-exception License_expired of license
-(** Thrown if the license in a given file is found to be expired *)
-exception License_file_deprecated
-(** Thrown if an old-style, deprecated license file is used *)
-
-val sku_and_name_of_edition : string -> string * string
-(** map an edition string to an sku name and a marketing name *)
-
-val to_assoc_list : license -> (string * string) list
(** Converts a license into a association list to place in DB. *)
-val of_assoc_list : (string * string) list -> license
+val to_assoc_list : license -> (string * string) list
+
(** Converts a license association list from DB into a license value. *)
+val of_assoc_list : (string * string) list -> license
+
+(** Returns a default, free license with 30-day grace. *)
val default : unit -> license
-(** Returns a default, free license with 30-day grace *)
-val license_valid : unit -> bool
+(** Check whether a given license is valid or expired. *)
+val check_expiry : license -> bool
+
(** Check whether the current license is valid or expired.
* Called from xapi/license_check.ml. *)
-val read_license_file : string -> license option
-(** Parse a license file and return the result. Only works for free-license
- * activation keys since XS 6.0.
- * Called from host.license_apply. *)
-val do_parse_and_validate : string -> unit
-(** As read_license_file, but also set license state variable if not expired.
- * Only works for free-license activation keys since XS 6.0.
- * Called from host.license_apply. *)
+val license_valid : unit -> bool
+
+(** Thrown if we fail to find a license param. *)
+exception Missing_license_param of string
+
+(** Obtain a date that lies 30 days in the future to set as grace expiry date. *)
+val grace_expiry : unit -> float
-val initialise : __context:Context.t -> host:[ `host ] Ref.t -> unit
-(** Initialises licensing on xapi start up *)
+(** Obtain a date that lies 30 days in the future to set as upgrade grace expiry date. *)
+val upgrade_grace_expiry : unit -> float
let existing_license_params = Db.Host.get_license_params ~__context ~self:host in
let existing_edition = Db.Host.get_edition ~__context ~self:host in
let default = default () in
- let free = Edition.to_string Edition.Free in
- let new_license =
- try
- let existing_license = of_assoc_list existing_license_params in
- try match Edition.of_string existing_edition with
- | Edition.Free ->
- (* old Floodgate-free behaviour *)
- begin try
- License_file.do_parse_and_validate !License_file.filename;
- info "Existing free license with expiry date %s still in effect." (Date.to_string (Date.of_float !license.expiry));
- !license (* do_parse_and_validate already sets !license *)
- with
- | License_file.License_expired l -> l (* keep expired license *)
- | _ ->
- (* activation file does not exist or is invalid *)
- if existing_license.expiry < default.expiry then begin
- info "Existing free license with expiry date %s still in effect." (Date.to_string (Date.of_float existing_license.expiry));
- {default with expiry = existing_license.expiry}
- end else begin
- info "Generating new free license, which needs to be activated in 30 days.";
- default
- end
+ let new_license = try
+ let existing_license = of_assoc_list existing_license_params in
+ match existing_edition with
+ | "free" ->
+ (* old Floodgate-free behaviour *)
+ begin try
+ License_file.do_parse_and_validate !License_file.filename;
+ info "Existing free license with expiry date %s still in effect." (Date.to_string (Date.of_float !license.expiry));
+ !license (* do_parse_and_validate already sets !license *)
+ with
+ | License_file.License_expired l -> l (* keep expired license *)
+ | _ ->
+ (* activation file does not exist or is invalid *)
+ if existing_license.expiry < default.expiry then begin
+ info "Existing free license with expiry date %s still in effect." (Date.to_string (Date.of_float existing_license.expiry));
+ {default with expiry = existing_license.expiry}
+ end else begin
+ info "Generating new free license, which needs to be activated in 30 days.";
+ default
end
- | edition ->
- (* existing license is a v6 Essentials license -> try to check one out again *)
- begin try
- V6client.get_v6_license ~__context ~host ~edition;
- with _ -> error "The license-server connection details (address or port) were missing or incomplete." end;
- begin match !V6client.licensed with
- | None ->
- let upgrade_grace = read_grace_from_file () > Unix.time () in
- if upgrade_grace then begin
- info "No %s license is available, but we are still in the upgrade grace period." existing_edition;
- {existing_license with grace = "upgrade grace"}
- end else begin
- info "No %s license is available. Essentials features have been disabled." existing_edition;
- {existing_license with expiry = 0.} (* expiry date 0 means 01-01-1970, so always expired *)
- end
- | Some license ->
- info "Successfully checked out %s license." existing_edition;
- (* delete upgrade-grace file, if it exists *)
- Unixext.unlink_safe Xapi_globs.upgrade_grace_file;
- if !V6client.grace then begin
- Grace_retry.retry_periodically host existing_edition;
- {existing_license with grace = "regular grace"; expiry = !V6client.expires}
- end else
- {existing_license with grace = "no"; expiry = !V6client.expires}
+ end
+ | "enterprise" | "platinum" | "enterprise-xd" ->
+ (* existing license is a v6 Essentials license -> try to check one out again *)
+ begin try
+ V6client.get_v6_license ~__context ~host ~edition:existing_edition;
+ with _ -> error "The license-server connection details (address or port) were missing or incomplete." end;
+ begin match !V6client.licensed with
+ | None ->
+ let upgrade_grace = read_grace_from_file () > Unix.time () in
+ if upgrade_grace then begin
+ info "No %s license is available, but we are still in the upgrade grace period." existing_edition;
+ {existing_license with grace = "upgrade grace"}
+ end else begin
+ info "No %s license is available. Essentials features have been disabled." existing_edition;
+ {existing_license with expiry = 0.} (* expiry date 0 means 01-01-1970, so always expired *)
end
- with Edition.Undefined_edition _ ->
- warn "Edition field corrupted; generating a new free license, which needs to be activated in 30 days.";
- default
- with License.Missing_license_param _ ->
+ | Some license ->
+ info "Successfully checked out %s license." existing_edition;
+ (* delete upgrade-grace file, if it exists *)
+ Unixext.unlink_safe Xapi_globs.upgrade_grace_file;
+ if !V6client.grace then begin
+ Grace_retry.retry_periodically host existing_edition;
+ {existing_license with grace = "regular grace"; expiry = !V6client.expires}
+ end else
+ {existing_license with grace = "no"; expiry = !V6client.expires}
+ end
+ | "" ->
+ (* upgrade from pre-MNR *)
+ if existing_license.sku = "XE Express" then begin
+ info "Upgrade from free: set to free edition.";
+ (* all existing license_params are kept; only fill in edition field *)
+ Db.Host.set_edition ~__context ~self:host ~value:"free";
+ {default with sku = "free"; expiry = existing_license.expiry}
+ end else begin
+ info "Upgrade from Essentials: transition to enterprise edition (30-day grace license).";
+ Db.Host.set_edition ~__context ~self:host ~value:"enterprise";
+ let expiry = upgrade_grace_expiry () in
+ write_grace_to_file expiry;
+ Unixext.unlink_safe !License_file.filename;
+ V6alert.send_v6_upgrade_grace_license ();
+ let name = Edition.to_marketing_name Edition.Enterprise in
+ {default with sku = "enterprise"; expiry = expiry; grace = "upgrade grace"; sku_marketing_name = name}
+ end
+ | _ ->
+ warn "Edition field corrupted; generating a new free license, which needs to be activated in 30 days.";
+ default
+ with _ ->
(* no license_params -> first boot *)
- Db.Host.set_edition ~__context ~self:host ~value:free;
+ Db.Host.set_edition ~__context ~self:host ~value:"free";
begin try
License_file.do_parse_and_validate !License_file.filename;
info "Found a free-license activation key with expiry date %s." (Date.to_string (Date.of_float !license.expiry));
let v6rpc xml = Xmlrpcclient.do_xml_rpc_unix ~version:"1.0" ~filename:socket ~path:"/" xml
(* conversion to v6 edition codes *)
-let editions = ["enterprise", "ENT"; "platinum", "PLT"]
+let editions = ["enterprise", "ENT"; "platinum", "PLT"; "enterprise-xd", "XD"]
(* reset to not-licensed state *)
let reset_state () =
config_file_sync \
config_file_io \
slave_backup \
- ../license/restrictions \
sm_fs_ops \
vmopshelpers \
vm_config \
bios_strings \
xapi_config \
../license/grace_retry \
- ../license/v6alert
+ ../license/v6alert \
+ ../license/edition \
+ ../license/features \
+ ../license/license_file \
+ ../license/license_init
OCamlProgram(xapi, $(XAPI_MODULES))
OCamlDocProgram(xapi, $(XAPI_MODULES))
type host_license = {
hostname: string;
uuid: string;
- rstr: Restrictions.restrictions;
+ rstr: Features.feature list;
license: License.license
}
let host_license_of_r host_r =
let params = host_r.API.host_license_params in
- let rstr = Restrictions.of_assoc_list params in
+ let rstr = Features.of_assoc_list params in
let license = License.of_assoc_list params in
{ hostname = host_r.API.host_hostname;
uuid = host_r.API.host_uuid;
(* Sort licenses into nearest-expiry first then free *)
let host_licenses = List.sort (fun a b ->
let a_expiry = a.license.License.expiry and b_expiry = b.license.License.expiry in
- let a_free = Restrictions.is_floodgate_free (Restrictions.sku_of_string a.license.License.sku)
- and b_free = Restrictions.is_floodgate_free (Restrictions.sku_of_string b.license.License.sku) in
+ let a_free = (Edition.of_string a.license.License.sku) = Edition.Free
+ and b_free = (Edition.of_string b.license.License.sku) = Edition.Free in
if a_expiry < b_expiry then -1
else
if a_expiry > b_expiry then 1
let now = Unix.gettimeofday () in
let hosts = List.map (fun h -> [ h.hostname;
String.sub h.uuid 0 8;
- Restrictions.to_compact_string h.rstr;
- Restrictions.obfuscated_string_of_sku (Restrictions.sku_of_string h.license.License.sku);
- string_of_bool (Restrictions.is_floodgate_free (Restrictions.sku_of_string h.license.License.sku));
+ Features.to_compact_string h.rstr;
+ Edition.to_short_string (Edition.of_string h.license.License.sku);
+ string_of_bool ((Edition.of_string h.license.License.sku) = Edition.Free);
Date.to_string (Date.of_float h.license.License.expiry);
Printf.sprintf "%.1f" ((h.license.License.expiry -. now) /. (24. *. 60. *. 60.));
]) host_licenses in
String.sub host_r.API.host_uuid 0 8;
"-"; "-"; "-"; "-"; "-" ]) invalid in
let __context = Context.make "diagnostic_license_status" in
- let pool_restrictions = Restrictions.get_pool ~__context in
- let pool_free = List.fold_left (||) false (List.map (fun h -> Restrictions.is_floodgate_free (Restrictions.sku_of_string h.license.License.sku)) host_licenses) in
+ let pool = List.hd (Db.Pool.get_all ~__context) in
+ let pool_features = Features.of_assoc_list (Db.Pool.get_restrictions ~__context ~self:pool) in
+ let pool_free = List.fold_left (||) false (List.map (fun h -> (Edition.of_string h.license.License.sku) = Edition.Free) host_licenses) in
let divider = [ "-"; "-"; "-"; "-"; "-"; "-"; "-" ] in
- let pool = [ "-"; "-"; Restrictions.to_compact_string pool_restrictions; "-"; string_of_bool pool_free; "-"; "-" ] in
+ let pool = [ "-"; "-"; Features.to_compact_string pool_features; "-"; string_of_bool pool_free; "-"; "-" ] in
let table = heading :: divider :: hosts @ invalid_hosts @ [ divider; pool ] in
(* Compute the required column widths *)
let allowed_xsdata (x, _) = List.fold_left (||) false (List.map (fun p -> String.startswith p x) [ "vm-data/"; "FIST/" ]) in
let xsdata = List.filter allowed_xsdata xsdata in
- let rstr = Restrictions.get () in
let platformdata =
let p = Db.VM.get_platform ~__context ~self in
- if rstr.Restrictions.platform_filter then List.filter (fun (k, v) -> List.mem k filtered_platform_flags) p else p
+ if not (Features.is_enabled ~__context Features.No_platform_filter) then
+ List.filter (fun (k, v) -> List.mem k filtered_platform_flags) p
+ else p
in
(* XXX: add extra configuration info to the platform/ map for now.
Eventually we'll put this somewhere where the guest can't see it. *)
split_host_port url
let assert_wlb_licensed ~__context =
- if not (Restrictions.license_ok_for_wlb ~__context)
+ if not (Features.is_enabled ~__context Features.WLB)
then
raise_license_restriction()
Server_helpers.exec_with_new_task "Licensing host"
(fun __context ->
let host = Helpers.get_localhost ~__context in
- License.initialise ~__context ~host;
+ License_init.initialise ~__context ~host;
(* Copy resulting license to the database *)
- Xapi_host.copy_license_to_db ~__context
+ Xapi_host.copy_license_to_db ~__context ~host
)
(** Writes the memory policy to xenstore and triggers the ballooning daemon. *)
"Starting periodic scheduler", [Startup.OnThread], Xapi_periodic_scheduler.loop;
"Remote requests", [Startup.OnThread], Remote_requests.handle_requests;
];
- let (_: Restrictions.restrictions) = Restrictions.get () in
begin match Pool_role.get_role () with
| Pool_role.Master ->
()
in
let configargs = [
- "license_filename", Config.Set_string License.filename;
+ "license_filename", Config.Set_string License_file.filename;
"http-port", Config.Set_int http_port;
"stunnelng", Config.Set_bool Stunnel.use_new_stunnel;
"log", Config.String set_log;
debug "build_number: %s" Version.build_number;
debug "hg changeset: %s" Version.hg_id;
debug "version: %d.%d" version_major version_minor;
- debug "License filename: %s" !License.filename
+ debug "License filename: %s" !License_file.filename
let event_hook_auth_on_xapi_initialize_succeeded = ref false
-(** Contains an XML key/value pair database containing the mapping from sku_type to sku_marketing_name *)
-let sku_marketing_name_db = "/etc/xensource/sku.db"
-
(** Directory used by the v6 license policy engine for caching *)
let upgrade_grace_file = "/var/xapi/ugp"
if Db.Pool.get_ha_enabled ~__context ~self:pool
then raise (Api_errors.Server_error(Api_errors.ha_is_enabled, []));
- if not ((Restrictions.get_pool ~__context).Restrictions.enable_xha)
+ if not (Features.is_enabled ~__context Features.HA)
then raise (Api_errors.Server_error(Api_errors.license_restriction, []));
(* Check that all of our 'disallow_unplug' PIFs are currently attached *)
let set_license_params ~__context ~self ~value =
Db.Host.set_license_params ~__context ~self ~value;
- Restrictions.update_pool_restrictions ~__context
+ Features.update_pool_features ~__context
let set_power_on_mode ~__context ~self ~power_on_mode ~power_on_config =
Db.Host.set_power_on_mode ~__context ~self ~value:power_on_mode;
raise (Api_errors.Server_error (Api_errors.not_implemented, [ "list_method" ]))
exception Pool_record_expected_singleton
-let copy_license_to_db ~__context =
+let copy_license_to_db ~__context ~host =
let license_kvpairs = License.to_assoc_list !License.license in
- let restrict_kvpairs = Restrictions.to_assoc_list (Restrictions.get ()) in
+ let edition = Edition.of_string (Db.Host.get_edition ~__context ~self:host) in
+ let features = Edition.to_features edition in
+ let restrict_kvpairs = Features.to_assoc_list features in
let license_params = license_kvpairs @ restrict_kvpairs in
Helpers.call_api_functions ~__context
(fun rpc session_id ->
let edition = Db.Host.get_edition ~__context ~self:host in
if edition <> "free" then raise (Api_errors.Server_error(Api_errors.activation_while_not_free, []));
let license = Base64.decode contents in
- let tmp = Filenameext.temp_file_in_dir !License.filename in
+ let tmp = Filenameext.temp_file_in_dir !License_file.filename in
let fd = Unix.openfile tmp [Unix.O_WRONLY; Unix.O_CREAT] 0o644 in
let close =
let already_done = ref false in
close ();
(* if downgrade and in a pool [i.e. a slave, or a master with >1 host record] then fail *)
- let new_license = match License.read_license_file tmp with
+ let new_license = match License_file.read_license_file tmp with
| Some l -> l
| None -> failwith "Failed to read license" (* Gets translated into generic failure below *)
in
(* CA-27011: if HA is enabled block the application of a license whose SKU does not support HA *)
- let new_restrictions = Restrictions.restrictions_of_sku (Restrictions.sku_of_string new_license.License.sku) in
+ let new_features = Edition.to_features (Edition.of_string new_license.License.sku) in
let pool = List.hd (Db.Pool.get_all ~__context) in
- if Db.Pool.get_ha_enabled ~__context ~self:pool && (not new_restrictions.Restrictions.enable_xha)
+ if Db.Pool.get_ha_enabled ~__context ~self:pool && (not (List.mem Features.HA new_features))
then raise (Api_errors.Server_error(Api_errors.ha_is_enabled, []));
- License.do_parse_and_validate tmp; (* throws exception if not valid which should really be translated into API error here.. *)
- Unix.rename tmp !License.filename; (* atomically overwrite host license file *)
- copy_license_to_db ~__context; (* update pool.license_params for clients that want to see license info through API *)
+ License_file.do_parse_and_validate tmp; (* throws exception if not valid which should really be translated into API error here.. *)
+ Unix.rename tmp !License_file.filename; (* atomically overwrite host license file *)
+ copy_license_to_db ~__context ~host; (* update pool.license_params for clients that want to see license info through API *)
Unixext.unlink_safe tmp;
with
_ as e ->
close ();
Unixext.unlink_safe tmp;
match e with
- | License.License_expired l -> raise (Api_errors.Server_error(Api_errors.license_expired, []))
- | License.License_file_deprecated -> raise (Api_errors.Server_error(Api_errors.license_file_deprecated, []))
+ | License_file.License_expired l -> raise (Api_errors.Server_error(Api_errors.license_expired, []))
+ | License_file.License_file_deprecated -> raise (Api_errors.Server_error(Api_errors.license_file_deprecated, []))
| (Api_errors.Server_error (x,y)) -> raise (Api_errors.Server_error (x,y)) (* pass through api exceptions *)
| e ->
begin
raise (Api_errors.Server_error (Api_errors.ha_is_enabled, []))
else begin
V6client.release_v6_license ();
- Unixext.unlink_safe !License.filename; (* delete activation key, if it exists *)
+ Unixext.unlink_safe !License_file.filename; (* delete activation key, if it exists *)
default (* default is free edition with 30 day grace validity *)
end
end
- | "enterprise" | "platinum" ->
+ | "enterprise" | "platinum" | "enterprise-xd" ->
(* Try to get the a v6 license; if one has already been checked out,
* it will be automatically checked back in. *)
if current_edition = "free" then
V6client.release_v6_license ();
raise (Api_errors.Server_error (Api_errors.license_checkout_error, [edition]))
| Some license ->
- let sku, name = License.sku_and_name_of_edition edition in
- let basic = {default with License.sku = sku; License.sku_marketing_name = name;
+ let name = Edition.to_marketing_name (Edition.of_string edition) in
+ let basic = {default with License.sku = edition; License.sku_marketing_name = name;
License.expiry = !V6client.expires} in
if !V6client.grace then begin
Grace_retry.retry_periodically host edition;
in
License.license := new_license;
Db.Host.set_edition ~__context ~self:host ~value:edition;
- copy_license_to_db ~__context; (* update host.license_params, pool sku and restrictions *)
+ copy_license_to_db ~__context ~host; (* update host.license_params, pool sku and restrictions *)
Unixext.unlink_safe Xapi_globs.upgrade_grace_file (* delete upgrade-grace file, if it exists *)
let refresh_pack_info ~__context ~host =
let set_cpu_features ~__context ~host ~features =
debug "Set CPU features";
(* check restrictions *)
- if not (Restrictions.ok_for_cpu_masking ~__context) then
+ if not (Features.is_enabled ~__context Features.CPU_masking) then
raise (Api_errors.Server_error (Api_errors.feature_restricted, []));
let cpuid = Cpuid.read_cpu_info () in
val get_log : __context:'a -> host:'b -> 'c
val send_debug_keys : __context:'a -> host:'b -> keys:string -> unit
val list_methods : __context:'a -> 'b
-val copy_license_to_db : __context:Context.t -> unit
+val copy_license_to_db : __context:Context.t -> host:[ `host ] Ref.t -> unit
val is_slave : __context:'a -> host:'b -> bool
(** Contact the host and return whether it is a slave or not.
let handle_message ~__context message =
try
- if not (Restrictions.get_pool ~__context).Restrictions.enable_email
+ if not (Features.is_enabled ~__context Features.Email)
then info "Email alerting is restricted by current license: not generating email"
else begin
let output, log = Forkhelpers.execute_command_get_output (Xapi_globs.xapi_message_script) [message] in
raise (Api_errors.Server_error(Api_errors.ha_is_enabled, []));
end in
- (* CA-26975: Pool restrictions (implied by pool_sku) MUST match *)
+ (* CA-26975: Pool edition MUST match *)
let assert_restrictions_match () =
let host_records = List.map snd (Client.Host.get_all_records ~rpc ~session_id) in
(* check pool edition *)
let pool_editions = List.map (fun host_r -> host_r.API.host_edition) host_records in
let edition_to_int = function
| "platinum" -> 2
- | "enterprise" -> 1
+ | "enterprise" | "enterprise-xd" -> 1
| "free" | _ -> 0
in
let int_to_edition = function
error "Remote has %s" (int_to_edition pool_edition);
error "Local has %s" (int_to_edition my_edition);
raise (Api_errors.Server_error(Api_errors.license_restriction, []))
- end;
- (* check pool restrictions *)
- let pool_license_params = List.map (fun host_r -> host_r.API.host_license_params) host_records in
- let pool_restrictions = Restrictions.pool_restrictions_of_list (List.map Restrictions.of_assoc_list pool_license_params) in
- let my_restrictions = Restrictions.get() in
- if pool_restrictions <> my_restrictions then begin
- error "Pool.join failed because of license restrictions mismatch";
- error "Remote has %s" (Restrictions.to_compact_string pool_restrictions);
- error "Local has %s" (Restrictions.to_compact_string my_restrictions);
- raise (Api_errors.Server_error(Api_errors.license_restriction, []))
end
in
(* CP-1224: Free Edition: Newly created subjects will have the Pool Administrator role. *)
(* CP-1224: Paid-for Edition: Newly created subjects will have an empty role. *)
let default_roles =
- if (Restrictions.license_ok_for_rbac ~__context)
+ if (Features.is_enabled ~__context Features.RBAC)
then (* paid-for edition: we can only create a subject with no roles*)
[]
else (*free edition: one fixed role of pool-admin only*)
(* CP-1224: Free Edition: Attempts to add or remove roles *)
(* will fail with a LICENSE_RESTRICTION error.*)
- if (not (Restrictions.license_ok_for_rbac ~__context)) then
+ if (not (Features.is_enabled ~__context Features.RBAC)) then
raise (Api_errors.Server_error(Api_errors.license_restriction, []))
else
(* CP-1224: Free Edition: Attempts to add or remove roles *)
(* will fail with a LICENSE_RESTRICTION error.*)
- if (not (Restrictions.license_ok_for_rbac ~__context)) then
+ if not (Features.is_enabled ~__context Features.RBAC) then
raise (Api_errors.Server_error(Api_errors.license_restriction, []))
else
(* As the checkpoint operation modify the domain state, we take the vm_lock to do not let the event *)
(* thread mess around with that. *)
let checkpoint ~__context ~vm ~new_name =
- if not (Restrictions.ok_for_checkpoint ~__context) then
+ if not (Features.is_enabled ~__context Features.Checkpoint) then
raise (Api_errors.Server_error(Api_errors.license_restriction, []))
else begin
Local_work_queue.wait_in_line Local_work_queue.long_running_queue
let assert_valid_for_current_context ~__context ~vm ~constraints =
(* NB we don't want to prevent dom0 ballooning even if we do want to prevent
domU ballooning. *)
- (if Db.VM.get_is_control_domain ~__context ~self:vm || (Restrictions.context_ok_for_dmc ~__context)
+ (if Db.VM.get_is_control_domain ~__context ~self:vm || (Features.is_enabled ~__context Features.DMC)
then assert_valid
else assert_valid_and_pinned_at_static_max)
~constraints
;;
edition) # for host-apply-edition (licensing)
IFS=$'\n,'
- COMPREPLY=( $(compgen -W "free ,enterprise ,platinum " -- ${value}) )
+ COMPREPLY=( $(compgen -W "free ,enterprise ,platinum ,enterprise-xd " -- ${value}) )
return 0
;;
*)