# HG changeset patch
# User Rob Hoes <rob.hoes@xxxxxxxxxx>
# Date 1279116492 -3600
# Node ID 657b16d89d927a395a77db15f7bfaeb6157e2436
# Parent 6854f26624a11f7138900d4d84fb0ad4302e44ed
Rewrite feature restrictions code
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@xxxxxxxxxx>
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/edition.ml
--- /dev/null
+++ b/ocaml/license/edition.ml
@@ -0,0 +1,38 @@
+(* (C) 2006-2010 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.
+ *)
+
+open Features
+
+(* Editions definitions *)
+
+type edition = Free
+exception Undefined_edition of string
+
+let of_string = function
+ | "free" | "XE Express" -> Free
+ | x -> raise (Undefined_edition x)
+
+let to_string = function
+ | Free -> "free"
+
+let to_short_string = function
+ | Free -> "FREE"
+
+let to_marketing_name = function
+ | Free -> "Xen Cloud Platform"
+
+(* Editions to features *)
+
+let to_features = function
+ | Free -> all_features
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/edition.mli
--- /dev/null
+++ b/ocaml/license/edition.mli
@@ -0,0 +1,39 @@
+(*
+ * 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.
+ *)
+(** Module that controls product edition and feature mappings.
+ * @group Licensing
+ *)
+
+(** Available editions *)
+type edition =
+ | Free (** Default 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}. *)
+val of_string : string -> edition
+
+(** Convert an {!edition} to a string. *)
+val to_string : edition -> string
+
+(** Convert an {!edition} to an abbreviated string. *)
+val to_short_string : edition -> string
+
+(** Convert an {!edition} to its marketing name. *)
+val to_marketing_name : edition -> string
+
+(** Get the list of {!feature}s enabled for a given {!edition}. *)
+val to_features : edition -> Features.feature list
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/features.ml
--- /dev/null
+++ b/ocaml/license/features.ml
@@ -0,0 +1,118 @@
+(* (C) 2006-2010 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.
+ *)
+
+open Listext
+module D = Debug.Debugger(struct let name="license" end)
+open D
+
+(* Features and restrictions *)
+
+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
+
+type orientation = Positive | Negative
+
+let keys_of_features =
+ [
+ VLAN, ("restrict_vlan", Negative, "VLAN");
+ QoS, ("restrict_qos", Negative, "QoS");
+ Shared_storage, ("restrict_pool_attached_storage", Negative,
"SStorage");
+ Netapp, ("restrict_netapp", Negative, "NTAP");
+ Equalogic, ("restrict_equalogic", Negative, "EQL");
+ Pooling, ("restrict_pooling", Negative, "Pool");
+ HA, ("enable_xha", Positive, "XHA");
+ Marathon, ("restrict_marathon", Negative, "MTC");
+ Email, ("restrict_email_alerting", Negative, "email");
+ Performance, ("restrict_historical_performance", Negative,
"perf");
+ WLB, ("restrict_wlb", Negative, "WLB");
+ RBAC, ("restrict_rbac", Negative, "RBAC");
+ DMC, ("restrict_dmc", Negative, "DMC");
+ Checkpoint, ("restrict_checkpoint", Negative, "chpt");
+ Vswitch_controller, ("restrict_vswitch_controller", Negative,
"DVSC");
+ CPU_masking, ("restrict_cpu_masking", Negative, "Mask");
+ Connection, ("restrict_connection", Negative, "Cnx");
+ No_platform_filter, ("platform_filter", Negative, "Plat");
+ No_nag_dialog, ("regular_nag_dialog", Negative, "nonag");
+ ]
+
+let string_of_feature f =
+ let str, o, _ = List.assoc f keys_of_features in
+ str, o
+
+let feature_of_string str =
+ let f, (_, o, _) = List.find (fun (_, (k, _, _)) -> str = k)
keys_of_features in
+ f, o
+
+let tag_of_feature f =
+ let _, _, tag = List.assoc f keys_of_features in
+ tag
+
+let all_features =
+ List.map (fun (f, _) -> f) keys_of_features
+
+let to_compact_string (s: feature list) =
+ let get_tag f =
+ let tag = tag_of_feature f in
+ if List.mem f s then
+ tag
+ else
+ String.make (String.length tag) ' '
+ in
+ let tags = List.map get_tag all_features in
+ String.concat " " tags
+
+let to_assoc_list (s: feature list) =
+ let get_map f =
+ let str, o = string_of_feature f in
+ let switch = List.mem f s in
+ let switch = string_of_bool (if o = Positive then switch else
not switch) in
+ str, switch
+ in
+ List.map get_map all_features
+
+let of_assoc_list l =
+ let get_feature (k, v) =
+ try
+ let v = bool_of_string v in
+ let f, o = feature_of_string k in
+ let v = if o = Positive then v else not v in
+ if v then Some f else None
+ with _ ->
+ None
+ in
+ let features = List.map get_feature l in
+ List.fold_left (function ac -> function Some f -> f :: ac | None -> ac)
[] features
+
+let pool_features_of_list hosts =
+ List.fold_left List.intersect all_features hosts
+
+let get_pool_features ~__context =
+ let pool = List.hd (Db.Pool.get_all ~__context) in
+ of_assoc_list (Db.Pool.get_restrictions ~__context ~self:pool)
+
+let is_enabled ~__context f =
+ let pool_features = get_pool_features ~__context in
+ List.mem f pool_features
+
+let update_pool_features ~__context =
+ let pool = List.hd (Db.Pool.get_all ~__context) in
+ let pool_features = get_pool_features ~__context in
+ let hosts = List.map (fun (_, host_r) ->
host_r.API.host_license_params) (Db.Host.get_all_records ~__context) in
+ let new_features = pool_features_of_list (List.map of_assoc_list hosts)
in
+ if new_features <> pool_features then begin
+ info "Old pool features enabled: %s" (to_compact_string
pool_features);
+ info "New pool features enabled: %s" (to_compact_string
new_features);
+ Db.Pool.set_restrictions ~__context ~self:pool
~value:(to_assoc_list new_features)
+ end
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/features.mli
--- /dev/null
+++ b/ocaml/license/features.mli
@@ -0,0 +1,57 @@
+(*
+ * 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.
+ *)
+(** Module that controls feature restriction.
+ * @group Licensing
+ *)
+
+(** Features than can be enabled and disabled. *)
+type feature =
+| VLAN (** Enable VLAN. Currently not used. *)
+| QoS (** Enable QoS control. Currently not
used. *)
+| Shared_storage (** Enable shared storage. Currently not used?
*)
+| Netapp (** Enable use of NetApp SRs *)
+| Equalogic (** Enable use of Equalogic SRs *)
+| Pooling (** Enable pooling of hosts *)
+| HA (** Enable High Availability (HA) *)
+| Marathon (** Currently not used *)
+| Email (** Enable email alerting *)
+| Performance (** Currently not used? *)
+| WLB (** Enable Workload Balancing (WLB) *)
+| RBAC (** Enable Role-Based Access Control
(RBAC) *)
+| DMC (** Enable Dynamic Memory Control (DMC)
*)
+| Checkpoint (** Enable Checkpoint functionality *)
+| Vswitch_controller (** Enable use of a Distributed VSwitch (DVS)
Controller *)
+| CPU_masking (** Enable masking of CPU features *)
+| Connection (** Used by XenCenter *)
+| No_platform_filter (** Filter platform data *)
+| No_nag_dialog (** Used by XenCenter *)
+
+(** The list of all known features. *)
+val all_features : feature list
+
+(** Returns a compact list of the current restrictions. *)
+val to_compact_string : feature list -> string
+
+(** Convert a {!feature} list into an association list. *)
+val to_assoc_list : feature list -> (string * string) list
+
+(** Convert an association list of features into a {!feature} list. *)
+val of_assoc_list : (string * string) list -> feature list
+
+(** Check whether a given feature is currently enabled on the pool. *)
+val is_enabled : __context:Context.t -> feature -> bool
+
+(** Update the pool-level restrictions list in the database. *)
+val update_pool_features : __context:Context.t -> unit
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/license.ml
--- a/ocaml/license/license.ml
+++ b/ocaml/license/license.ml
@@ -11,18 +11,6 @@
* 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
@@ -30,11 +18,8 @@
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 =
@@ -59,27 +44,6 @@
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 =
@@ -168,7 +132,7 @@
let default () =
{
- sku = sku;
+ sku = Edition.to_string edition;
version = default_version;
serialnumber = "";
sockets = default_sockets;
@@ -183,217 +147,15 @@
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
+let license_valid () = check_expiry !license
-(* 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
-
- 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
-
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/license.mli
--- a/ocaml/license/license.mli
+++ b/ocaml/license/license.mli
@@ -17,86 +17,53 @@
(** 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 *)
+(** Converts a license into a association list to place in DB. *)
+val to_assoc_list : license -> (string * string) list
-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 *)
+(** Converts a license association list from DB into a license value. *)
+val of_assoc_list : (string * string) list -> license
-val sku_and_name_of_edition : string -> string * string
-(** map an edition string to an sku name and a marketing name *)
+(** Returns a default, free license with 30-day grace. *)
+val default : unit -> license
-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
-(** Converts a license association list from DB into a license value. *)
-val default : unit -> license
-(** Returns a default, free license with 30-day grace *)
+(** Check whether a given license is valid or expired. *)
+val check_expiry : license -> bool
-val license_valid : unit -> 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
-val initialise : __context:Context.t -> host:[ `host ] Ref.t -> unit
-(** Initialises licensing on xapi start up *)
+(** 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
+
+(** Obtain a date that lies 30 days in the future to set as upgrade grace
expiry date. *)
+val upgrade_grace_expiry : unit -> float
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/license_file.ml
--- /dev/null
+++ b/ocaml/license/license_file.ml
@@ -0,0 +1,143 @@
+(*
+ * 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.
+ *)
+
+(* Slightly outdated comment:
+ *
+ * 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
+open License
+
+module D = Debug.Debugger(struct let name="license" end)
+open D
+
+exception LicenseParseError
+exception LicenseCannotReadFile
+exception LicenseFieldMissing of string
+exception License_expired of license
+exception License_file_deprecated
+
+(* Set from config file: *)
+let filename = ref ""
+
+(* 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 5.6 *)
+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
+
+ 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 = Edition.to_string Edition.Free;
+ 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 = Edition.to_marketing_name Edition.Free;
+ }
+ | _ -> raise LicenseParseError
+
+(* only activation keys are accepted as license files since XS 5.6 *)
+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 5.6 *)
+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 (License.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
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/license_file.mli
--- /dev/null
+++ b/ocaml/license/license_file.mli
@@ -0,0 +1,43 @@
+(*
+ * 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.
+ *)
+(** Handling of license files
+ * @group Licensing
+ *)
+
+(** Path to the license file in use. *)
+val filename : string ref
+
+(** Parse a license file and return the result.
+ * Called from host.license_apply. *)
+val read_license_file : string -> License.license option
+
+(** As read_license_file, but also set license state variable if not expired.
+ * Called from host.license_apply. *)
+val do_parse_and_validate : string -> unit
+
+
+(** Thrown if the license data is malformed. *)
+exception LicenseParseError
+
+(** Thrown if the given license file cannot be opened. *)
+exception LicenseCannotReadFile
+
+(** Thrown if a particular field is missing from the license data. *)
+exception LicenseFieldMissing of string
+
+(** Thrown if the license in a given file is found to be expired. *)
+exception License_expired of License.license
+
+(** Thrown if an old-style, deprecated license file is used. *)
+exception License_file_deprecated
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/license_init.ml
--- /dev/null
+++ b/ocaml/license/license_init.ml
@@ -0,0 +1,117 @@
+(*
+ * 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.
+ *)
+
+open License
+
+module D = Debug.Debugger(struct let name="license" end)
+open D
+
+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
+ 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
+ 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
+ | 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";
+ 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));
+ !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
*)
+ info "Generating new free license, which needs
to be activated in 30 days.";
+ default
+ end
+ in
+ license := new_license
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/license_init.mli
--- /dev/null
+++ b/ocaml/license/license_init.mli
@@ -0,0 +1,20 @@
+(*
+ * 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.
+ *)
+(** Licensing initialisation
+ * @group Licensing
+ *)
+
+(** Initialises licensing on xapi start up *)
+val initialise : __context:Context.t -> host:API.ref_host -> unit
+
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/license/v6client.ml
--- a/ocaml/license/v6client.ml
+++ b/ocaml/license/v6client.ml
@@ -34,7 +34,7 @@
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 () =
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/OMakefile
--- a/ocaml/xapi/OMakefile
+++ b/ocaml/xapi/OMakefile
@@ -131,7 +131,6 @@
config_file_sync \
config_file_io \
slave_backup \
- ../license/restrictions \
sm_fs_ops \
vmopshelpers \
vm_config \
@@ -234,7 +233,11 @@
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))
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/cli_operations.ml
--- a/ocaml/xapi/cli_operations.ml
+++ b/ocaml/xapi/cli_operations.ml
@@ -141,12 +141,12 @@
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;
@@ -162,8 +162,8 @@
(* 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
@@ -175,9 +175,9 @@
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
@@ -185,10 +185,11 @@
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 *)
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/vmops.ml
--- a/ocaml/xapi/vmops.ml
+++ b/ocaml/xapi/vmops.ml
@@ -346,10 +346,11 @@
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. *)
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/workload_balancing.ml
--- a/ocaml/xapi/workload_balancing.ml
+++ b/ocaml/xapi/workload_balancing.ml
@@ -87,7 +87,7 @@
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()
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi.ml
--- a/ocaml/xapi/xapi.ml
+++ b/ocaml/xapi/xapi.ml
@@ -535,9 +535,9 @@
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. *)
@@ -763,7 +763,6 @@
"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 ->
()
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_config.ml
--- a/ocaml/xapi/xapi_config.ml
+++ b/ocaml/xapi/xapi_config.ml
@@ -98,7 +98,7 @@
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;
@@ -118,5 +118,5 @@
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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_globs.ml
--- a/ocaml/xapi/xapi_globs.ml
+++ b/ocaml/xapi/xapi_globs.ml
@@ -543,9 +543,6 @@
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"
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_ha.ml
--- a/ocaml/xapi/xapi_ha.ml
+++ b/ocaml/xapi/xapi_ha.ml
@@ -1366,7 +1366,7 @@
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 *)
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_host.ml
--- a/ocaml/xapi/xapi_host.ml
+++ b/ocaml/xapi/xapi_host.ml
@@ -35,7 +35,7 @@
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;
@@ -521,9 +521,11 @@
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 ->
@@ -563,7 +565,7 @@
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
@@ -577,28 +579,28 @@
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
@@ -1225,11 +1227,11 @@
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
@@ -1247,8 +1249,8 @@
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;
@@ -1262,7 +1264,7 @@
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 =
@@ -1273,7 +1275,7 @@
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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_host.mli
--- a/ocaml/xapi/xapi_host.mli
+++ b/ocaml/xapi/xapi_host.mli
@@ -65,7 +65,7 @@
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.
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_message.ml
--- a/ocaml/xapi/xapi_message.ml
+++ b/ocaml/xapi/xapi_message.ml
@@ -165,7 +165,7 @@
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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_pool.ml
--- a/ocaml/xapi/xapi_pool.ml
+++ b/ocaml/xapi/xapi_pool.ml
@@ -57,14 +57,14 @@
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
@@ -79,16 +79,6 @@
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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_subject.ml
--- a/ocaml/xapi/xapi_subject.ml
+++ b/ocaml/xapi/xapi_subject.ml
@@ -64,7 +64,7 @@
(* 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*)
@@ -177,7 +177,7 @@
(* 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
@@ -215,7 +215,7 @@
(* 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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_vm.ml
--- a/ocaml/xapi/xapi_vm.ml
+++ b/ocaml/xapi/xapi_vm.ml
@@ -920,7 +920,7 @@
(* 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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xapi/xapi_vm_memory_constraints.ml
--- a/ocaml/xapi/xapi_vm_memory_constraints.ml
+++ b/ocaml/xapi/xapi_vm_memory_constraints.ml
@@ -68,7 +68,7 @@
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
diff -r 6854f26624a1 -r 657b16d89d92 ocaml/xe-cli/bash-completion
--- a/ocaml/xe-cli/bash-completion
+++ b/ocaml/xe-cli/bash-completion
@@ -170,7 +170,7 @@
;;
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
;;
*)
ocaml/license/edition.ml | 38 ++++
ocaml/license/edition.mli | 39 ++++
ocaml/license/features.ml | 118 ++++++++++++++
ocaml/license/features.mli | 57 +++++++
ocaml/license/license.ml | 246 +------------------------------
ocaml/license/license.mli | 107 ++++--------
ocaml/license/license_file.ml | 143 ++++++++++++++++++
ocaml/license/license_file.mli | 43 +++++
ocaml/license/license_init.ml | 117 ++++++++++++++
ocaml/license/license_init.mli | 20 ++
ocaml/license/v6client.ml | 2 +-
ocaml/xapi/OMakefile | 7 +-
ocaml/xapi/cli_operations.ml | 21 +-
ocaml/xapi/vmops.ml | 5 +-
ocaml/xapi/workload_balancing.ml | 2 +-
ocaml/xapi/xapi.ml | 5 +-
ocaml/xapi/xapi_config.ml | 4 +-
ocaml/xapi/xapi_globs.ml | 3 -
ocaml/xapi/xapi_ha.ml | 2 +-
ocaml/xapi/xapi_host.ml | 38 ++--
ocaml/xapi/xapi_host.mli | 2 +-
ocaml/xapi/xapi_message.ml | 2 +-
ocaml/xapi/xapi_pool.ml | 14 +-
ocaml/xapi/xapi_subject.ml | 6 +-
ocaml/xapi/xapi_vm.ml | 2 +-
ocaml/xapi/xapi_vm_memory_constraints.ml | 2 +-
ocaml/xe-cli/bash-completion | 2 +-
27 files changed, 672 insertions(+), 375 deletions(-)
xen-api.hg-1.patch
Description: Text Data
_______________________________________________
xen-api mailing list
xen-api@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/mailman/listinfo/xen-api
|