[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-API] [PATCH 1 of 4] Rewrite feature restrictions code


  • To: xen-api@xxxxxxxxxxxxxxxxxxx
  • From: Rob Hoes <rob.hoes@xxxxxxxxxx>
  • Date: Wed, 14 Jul 2010 15:18:20 +0100
  • Delivery-date: Wed, 14 Jul 2010 07:28:15 -0700
  • List-id: Discussion of API issues surrounding Xen <xen-api.lists.xensource.com>

# 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(-)


Attachment: xen-api.hg-1.patch
Description: Text Data

_______________________________________________
xen-api mailing list
xen-api@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/mailman/listinfo/xen-api

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.