Skip to content

Commit

Permalink
vcsim: LicenseManager enhancements
Browse files Browse the repository at this point in the history
- add ProductName + ProductVersion properties to default EvalLicense

- QueryAssignedLicenses defaults to listing license for all hosts and clusters

- govc object.save '-l' flag will include all LicenseManager properties and
  LicenseAssignmentManager QueryAssignedLicenses response

Signed-off-by: Doug MacEachern <[email protected]>
  • Loading branch information
dougm committed Dec 12, 2024
1 parent 675534a commit 4aae3ca
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 35 deletions.
1 change: 1 addition & 0 deletions govc/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4997,6 +4997,7 @@ Options:
-d= Save objects in directory
-f=false Remove existing object directory
-folder= Inventory folder [GOVC_FOLDER]
-l=false Include license properties
-r=true Include children of the container view root
-type=[] Resource types to save. Defaults to all types
-v=false Verbose output
Expand Down
37 changes: 31 additions & 6 deletions govc/object/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type save struct {
verbose bool
recurse bool
one bool
license bool
kind kinds
summary map[string]int
}
Expand All @@ -60,6 +61,7 @@ func (cmd *save) Register(ctx context.Context, f *flag.FlagSet) {
f.BoolVar(&cmd.one, "1", false, "Save ROOT only, without its children")
f.StringVar(&cmd.dir, "d", "", "Save objects in directory")
f.BoolVar(&cmd.force, "f", false, "Remove existing object directory")
f.BoolVar(&cmd.license, "l", false, "Include license properties")
f.BoolVar(&cmd.recurse, "r", true, "Include children of the container view root")
f.Var(&cmd.kind, "type", "Resource types to save. Defaults to all types")
f.BoolVar(&cmd.verbose, "v", false, "Verbose output")
Expand Down Expand Up @@ -168,13 +170,22 @@ func saveAlarmManager(ctx context.Context, c *vim25.Client, ref types.ManagedObj
return []saveMethod{{"GetAlarm", res}, {"", content}}, nil
}

func saveLicenseAssignmentManager(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
res, err := methods.QueryAssignedLicenses(ctx, c, &types.QueryAssignedLicenses{This: ref})
if err != nil {
return nil, err
}
return []saveMethod{{"QueryAssignedLicenses", res}}, nil
}

// saveObjects maps object types to functions that can save data that isn't available via the PropertyCollector
var saveObjects = map[string]func(context.Context, *vim25.Client, types.ManagedObjectReference) ([]saveMethod, error){
"VmwareDistributedVirtualSwitch": saveDVS,
"EnvironmentBrowser": saveEnvironmentBrowser,
"HostNetworkSystem": saveHostNetworkSystem,
"HostSystem": saveHostSystem,
"AlarmManager": saveAlarmManager,
"LicenseAssignmentManager": saveLicenseAssignmentManager,
}

func (cmd *save) save(content []types.ObjectContent) error {
Expand Down Expand Up @@ -226,6 +237,10 @@ func (cmd *save) save(content []types.ObjectContent) error {
}

func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
if f.NArg() > 1 {
return flag.ErrHelp
}

cmd.summary = make(map[string]int)
c, err := cmd.Client()
if err != nil {
Expand Down Expand Up @@ -291,25 +306,35 @@ func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
for _, p := range content[0].PropSet {
if c, ok := p.Val.(types.ServiceContent); ok {
var path []string
var selectSet []types.BaseSelectionSpec
var propSet []types.PropertySpec
for _, ref := range mo.References(c) {
all := types.NewBool(true)
switch ref.Type {
case "LicenseManager":
// avoid saving "licenses" property as it includes the keys
path = []string{"licenseAssignmentManager"}
all = nil
selectSet = []types.BaseSelectionSpec{&types.TraversalSpec{
Type: ref.Type,
Path: "licenseAssignmentManager",
}}
propSet = []types.PropertySpec{{Type: "LicenseAssignmentManager", All: all}}
// avoid saving "licenses" property by default as it includes the keys
if cmd.license == false {
path = []string{selectSet[0].(*types.TraversalSpec).Path}
all, selectSet, propSet = nil, nil, nil
}
case "ServiceManager":
all = nil
}
req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
ObjectSet: []types.ObjectSpec{{
Obj: ref,
Obj: ref,
SelectSet: selectSet,
}},
PropSet: []types.PropertySpec{{
PropSet: append(propSet, types.PropertySpec{
Type: ref.Type,
All: all,
PathSet: path,
}},
}),
})
}
break
Expand Down
11 changes: 9 additions & 2 deletions govc/test/license.bats
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,18 @@ get_nlabel() {
assert_success

# Expect the test instance to run in evaluation mode
assert_equal "Evaluation Mode" "$(get_key 00000-00000-00000-00000-00000 <<<$output | jq -r ".name")"
mode="$(get_key 00000-00000-00000-00000-00000 <<<"$output" | jq -r ".name")"
assert_equal "Evaluation Mode" "$mode"

name=$(jq -r '.[].properties[] | select(.key == "ProductName") | .value' <<<"$output")
assert_equal "$(govc about -json | jq -r .about.licenseProductName)" "$name"

name=$(jq -r '.[].properties[] | select(.key == "ProductVersion") | .value' <<<"$output")
assert_equal "$(govc about -json | jq -r .about.licenseProductVersion)" "$name"
}

@test "license.decode" {
esx_env
vcsim_env

verify_evaluation

Expand Down
14 changes: 14 additions & 0 deletions govc/test/object.bats
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,20 @@ EOF
n=$(ls "$dir"/*.xml | wc -l)
rm -rf "$dir"
assert_equal 10 "$n"

run govc object.save -v -d "$dir"
assert_success

n=$(ls "$dir"/*License*.xml | wc -l)
rm -rf "$dir"
assert_equal 1 "$n" # LicenseManager

run govc object.save -l -v -d "$dir"
assert_success

n=$(ls "$dir"/*License*.xml | wc -l)
rm -rf "$dir"
assert_equal 2 "$n" # LicenseManager + LicenseAssignmentManager
}

@test "tree" {
Expand Down
156 changes: 131 additions & 25 deletions simulator/license_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package simulator

import (
"slices"

"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
Expand Down Expand Up @@ -51,7 +53,23 @@ type LicenseManager struct {
}

func (m *LicenseManager) init(r *Registry) {
m.Licenses = []types.LicenseManagerLicenseInfo{EvalLicense}
if len(m.Licenses) == 0 {
about := r.content().About
product := []types.KeyAnyValue{
{
Key: "ProductName",
Value: about.LicenseProductName,
},
{
Key: "ProductVersion",
Value: about.LicenseProductVersion,
},
}

EvalLicense.Properties = append(EvalLicense.Properties, product...)

m.Licenses = []types.LicenseManagerLicenseInfo{EvalLicense}
}

if r.IsVPX() {
if m.LicenseAssignmentManager == nil {
Expand All @@ -60,9 +78,16 @@ func (m *LicenseManager) init(r *Registry) {
Value: "LicenseAssignmentManager",
}
}
r.Put(&LicenseAssignmentManager{
mo.LicenseAssignmentManager{Self: *m.LicenseAssignmentManager},
})

lam := new(LicenseAssignmentManager)
lam.Self = *m.LicenseAssignmentManager
lam.QueryAssignedLicensesResponse.Returnval = []types.LicenseAssignmentManagerLicenseAssignment{{
EntityId: r.content().About.InstanceUuid,
EntityDisplayName: "vcsim",
AssignedLicense: EvalLicense,
}}
r.Put(lam)
r.AddHandler(lam)
}
}

Expand Down Expand Up @@ -102,6 +127,21 @@ func (m *LicenseManager) RemoveLicense(ctx *Context, req *types.RemoveLicense) s
return body
}

func (m *LicenseManager) DecodeLicense(ctx *Context, req *types.DecodeLicense) soap.HasFault {
body := &methods.DecodeLicenseBody{
Res: &types.DecodeLicenseResponse{},
}

for _, license := range m.Licenses {
if req.LicenseKey == license.LicenseKey {
body.Res.Returnval = license
break
}
}

return body
}

func (m *LicenseManager) UpdateLicenseLabel(ctx *Context, req *types.UpdateLicenseLabel) soap.HasFault {
body := &methods.UpdateLicenseLabelBody{}

Expand Down Expand Up @@ -141,42 +181,108 @@ func (m *LicenseManager) UpdateLicenseLabel(ctx *Context, req *types.UpdateLicen

type LicenseAssignmentManager struct {
mo.LicenseAssignmentManager

types.QueryAssignedLicensesResponse
}

var licensedTypes = []string{"HostSystem", "ClusterComputeResource"}

// PutObject assigns a license when a host or cluster is created.
func (m *LicenseAssignmentManager) PutObject(obj mo.Reference) {
ref := obj.Reference()

if !slices.Contains(licensedTypes, ref.Type) {
return
}

if slices.ContainsFunc(m.QueryAssignedLicensesResponse.Returnval,
func(am types.LicenseAssignmentManagerLicenseAssignment) bool {
return am.EntityId == ref.Value
}) {
return // via vcsim -load
}

la := types.LicenseAssignmentManagerLicenseAssignment{
EntityId: ref.Value,
Scope: Map.content().About.InstanceUuid,
EntityDisplayName: obj.(mo.Entity).Entity().Name,
AssignedLicense: EvalLicense,
}

m.QueryAssignedLicensesResponse.Returnval =
append(m.QueryAssignedLicensesResponse.Returnval, la)
}

// RemoveObject removes the license assignment when a host or cluster is removed.
func (m *LicenseAssignmentManager) RemoveObject(ctx *Context, ref types.ManagedObjectReference) {
if !slices.Contains(licensedTypes, ref.Type) {
return
}

m.QueryAssignedLicensesResponse.Returnval =
slices.DeleteFunc(m.QueryAssignedLicensesResponse.Returnval,
func(am types.LicenseAssignmentManagerLicenseAssignment) bool {
return am.EntityId == ref.Value
})
}

func (*LicenseAssignmentManager) UpdateObject(*Context, mo.Reference, []types.PropertyChange) {}

func (m *LicenseAssignmentManager) init(r *Registry) {
r.AddHandler(m)
}

func (m *LicenseAssignmentManager) QueryAssignedLicenses(ctx *Context, req *types.QueryAssignedLicenses) soap.HasFault {
body := &methods.QueryAssignedLicensesBody{
Res: &types.QueryAssignedLicensesResponse{},
}

// EntityId can be a HostSystem or the vCenter InstanceUuid
if req.EntityId != "" {
if req.EntityId != ctx.Map.content().About.InstanceUuid {
id := types.ManagedObjectReference{
Type: "HostSystem",
Value: req.EntityId,
}

if ctx.Map.Get(id) == nil {
return body
if req.EntityId == "" {
body.Res = &m.QueryAssignedLicensesResponse
} else {
for _, r := range m.QueryAssignedLicensesResponse.Returnval {
if r.EntityId == req.EntityId {
body.Res.Returnval = append(body.Res.Returnval, r)
}
}
}

body.Res.Returnval = []types.LicenseAssignmentManagerLicenseAssignment{
{
EntityId: req.EntityId,
AssignedLicense: EvalLicense,
},
}

return body
}

func (m *LicenseAssignmentManager) UpdateAssignedLicense(ctx *Context, req *types.UpdateAssignedLicense) soap.HasFault {
body := &methods.UpdateAssignedLicenseBody{
Res: &types.UpdateAssignedLicenseResponse{
Returnval: licenseInfo(req.LicenseKey, nil),
},
body := new(methods.UpdateAssignedLicenseBody)

var license *types.LicenseManagerLicenseInfo
lm := ctx.Map.Get(*ctx.Map.content().LicenseManager).(*LicenseManager)

for i, l := range lm.Licenses {
if l.LicenseKey == req.LicenseKey {
license = &lm.Licenses[i]
}
}

if license == nil {
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "entityId"})
return body
}

for i, r := range m.QueryAssignedLicensesResponse.Returnval {
if r.EntityId == req.Entity {
r.AssignedLicense = *license

if req.EntityDisplayName != "" {
r.EntityDisplayName = req.EntityDisplayName
}

m.QueryAssignedLicensesResponse.Returnval[i] = r

body.Res = &types.UpdateAssignedLicenseResponse{
Returnval: r.AssignedLicense,
}

break
}
}

return body
Expand Down
9 changes: 7 additions & 2 deletions simulator/license_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,13 @@ func TestLicenseManagerVPX(t *testing.T) {
t.Fatal(err)
}

if len(la) != 1 {
t.Fatal("no licenses")
expect := 1
if name == "" {
count := m.Count()
expect = count.Host + count.ClusterHost + count.Cluster + 1 // (1 == vCenter)
}
if len(la) != expect {
t.Fatalf("%d licenses", len(la))
}

if !reflect.DeepEqual(la[0].AssignedLicense, EvalLicense) {
Expand Down
1 change: 1 addition & 0 deletions simulator/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ var kinds = map[string]reflect.Type{
"HostSystem": reflect.TypeOf((*HostSystem)(nil)).Elem(),
"IpPoolManager": reflect.TypeOf((*IpPoolManager)(nil)).Elem(),
"LicenseManager": reflect.TypeOf((*LicenseManager)(nil)).Elem(),
"LicenseAssignmentManager": reflect.TypeOf((*LicenseAssignmentManager)(nil)).Elem(),
"OptionManager": reflect.TypeOf((*OptionManager)(nil)).Elem(),
"OvfManager": reflect.TypeOf((*OvfManager)(nil)).Elem(),
"PerformanceManager": reflect.TypeOf((*PerformanceManager)(nil)).Elem(),
Expand Down

0 comments on commit 4aae3ca

Please sign in to comment.