Skip to content

Commit 4aae3ca

Browse files
committed
vcsim: LicenseManager enhancements
- 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]>
1 parent 675534a commit 4aae3ca

File tree

7 files changed

+194
-35
lines changed

7 files changed

+194
-35
lines changed

govc/USAGE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4997,6 +4997,7 @@ Options:
49974997
-d= Save objects in directory
49984998
-f=false Remove existing object directory
49994999
-folder= Inventory folder [GOVC_FOLDER]
5000+
-l=false Include license properties
50005001
-r=true Include children of the container view root
50015002
-type=[] Resource types to save. Defaults to all types
50025003
-v=false Verbose output

govc/object/save.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type save struct {
4545
verbose bool
4646
recurse bool
4747
one bool
48+
license bool
4849
kind kinds
4950
summary map[string]int
5051
}
@@ -60,6 +61,7 @@ func (cmd *save) Register(ctx context.Context, f *flag.FlagSet) {
6061
f.BoolVar(&cmd.one, "1", false, "Save ROOT only, without its children")
6162
f.StringVar(&cmd.dir, "d", "", "Save objects in directory")
6263
f.BoolVar(&cmd.force, "f", false, "Remove existing object directory")
64+
f.BoolVar(&cmd.license, "l", false, "Include license properties")
6365
f.BoolVar(&cmd.recurse, "r", true, "Include children of the container view root")
6466
f.Var(&cmd.kind, "type", "Resource types to save. Defaults to all types")
6567
f.BoolVar(&cmd.verbose, "v", false, "Verbose output")
@@ -168,13 +170,22 @@ func saveAlarmManager(ctx context.Context, c *vim25.Client, ref types.ManagedObj
168170
return []saveMethod{{"GetAlarm", res}, {"", content}}, nil
169171
}
170172

173+
func saveLicenseAssignmentManager(ctx context.Context, c *vim25.Client, ref types.ManagedObjectReference) ([]saveMethod, error) {
174+
res, err := methods.QueryAssignedLicenses(ctx, c, &types.QueryAssignedLicenses{This: ref})
175+
if err != nil {
176+
return nil, err
177+
}
178+
return []saveMethod{{"QueryAssignedLicenses", res}}, nil
179+
}
180+
171181
// saveObjects maps object types to functions that can save data that isn't available via the PropertyCollector
172182
var saveObjects = map[string]func(context.Context, *vim25.Client, types.ManagedObjectReference) ([]saveMethod, error){
173183
"VmwareDistributedVirtualSwitch": saveDVS,
174184
"EnvironmentBrowser": saveEnvironmentBrowser,
175185
"HostNetworkSystem": saveHostNetworkSystem,
176186
"HostSystem": saveHostSystem,
177187
"AlarmManager": saveAlarmManager,
188+
"LicenseAssignmentManager": saveLicenseAssignmentManager,
178189
}
179190

180191
func (cmd *save) save(content []types.ObjectContent) error {
@@ -226,6 +237,10 @@ func (cmd *save) save(content []types.ObjectContent) error {
226237
}
227238

228239
func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
240+
if f.NArg() > 1 {
241+
return flag.ErrHelp
242+
}
243+
229244
cmd.summary = make(map[string]int)
230245
c, err := cmd.Client()
231246
if err != nil {
@@ -291,25 +306,35 @@ func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
291306
for _, p := range content[0].PropSet {
292307
if c, ok := p.Val.(types.ServiceContent); ok {
293308
var path []string
309+
var selectSet []types.BaseSelectionSpec
310+
var propSet []types.PropertySpec
294311
for _, ref := range mo.References(c) {
295312
all := types.NewBool(true)
296313
switch ref.Type {
297314
case "LicenseManager":
298-
// avoid saving "licenses" property as it includes the keys
299-
path = []string{"licenseAssignmentManager"}
300-
all = nil
315+
selectSet = []types.BaseSelectionSpec{&types.TraversalSpec{
316+
Type: ref.Type,
317+
Path: "licenseAssignmentManager",
318+
}}
319+
propSet = []types.PropertySpec{{Type: "LicenseAssignmentManager", All: all}}
320+
// avoid saving "licenses" property by default as it includes the keys
321+
if cmd.license == false {
322+
path = []string{selectSet[0].(*types.TraversalSpec).Path}
323+
all, selectSet, propSet = nil, nil, nil
324+
}
301325
case "ServiceManager":
302326
all = nil
303327
}
304328
req.SpecSet = append(req.SpecSet, types.PropertyFilterSpec{
305329
ObjectSet: []types.ObjectSpec{{
306-
Obj: ref,
330+
Obj: ref,
331+
SelectSet: selectSet,
307332
}},
308-
PropSet: []types.PropertySpec{{
333+
PropSet: append(propSet, types.PropertySpec{
309334
Type: ref.Type,
310335
All: all,
311336
PathSet: path,
312-
}},
337+
}),
313338
})
314339
}
315340
break

govc/test/license.bats

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,18 @@ get_nlabel() {
6666
assert_success
6767

6868
# Expect the test instance to run in evaluation mode
69-
assert_equal "Evaluation Mode" "$(get_key 00000-00000-00000-00000-00000 <<<$output | jq -r ".name")"
69+
mode="$(get_key 00000-00000-00000-00000-00000 <<<"$output" | jq -r ".name")"
70+
assert_equal "Evaluation Mode" "$mode"
71+
72+
name=$(jq -r '.[].properties[] | select(.key == "ProductName") | .value' <<<"$output")
73+
assert_equal "$(govc about -json | jq -r .about.licenseProductName)" "$name"
74+
75+
name=$(jq -r '.[].properties[] | select(.key == "ProductVersion") | .value' <<<"$output")
76+
assert_equal "$(govc about -json | jq -r .about.licenseProductVersion)" "$name"
7077
}
7178

7279
@test "license.decode" {
73-
esx_env
80+
vcsim_env
7481

7582
verify_evaluation
7683

govc/test/object.bats

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,20 @@ EOF
734734
n=$(ls "$dir"/*.xml | wc -l)
735735
rm -rf "$dir"
736736
assert_equal 10 "$n"
737+
738+
run govc object.save -v -d "$dir"
739+
assert_success
740+
741+
n=$(ls "$dir"/*License*.xml | wc -l)
742+
rm -rf "$dir"
743+
assert_equal 1 "$n" # LicenseManager
744+
745+
run govc object.save -l -v -d "$dir"
746+
assert_success
747+
748+
n=$(ls "$dir"/*License*.xml | wc -l)
749+
rm -rf "$dir"
750+
assert_equal 2 "$n" # LicenseManager + LicenseAssignmentManager
737751
}
738752

739753
@test "tree" {

simulator/license_manager.go

Lines changed: 131 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package simulator
1818

1919
import (
20+
"slices"
21+
2022
"github.com/vmware/govmomi/vim25/methods"
2123
"github.com/vmware/govmomi/vim25/mo"
2224
"github.com/vmware/govmomi/vim25/soap"
@@ -51,7 +53,23 @@ type LicenseManager struct {
5153
}
5254

5355
func (m *LicenseManager) init(r *Registry) {
54-
m.Licenses = []types.LicenseManagerLicenseInfo{EvalLicense}
56+
if len(m.Licenses) == 0 {
57+
about := r.content().About
58+
product := []types.KeyAnyValue{
59+
{
60+
Key: "ProductName",
61+
Value: about.LicenseProductName,
62+
},
63+
{
64+
Key: "ProductVersion",
65+
Value: about.LicenseProductVersion,
66+
},
67+
}
68+
69+
EvalLicense.Properties = append(EvalLicense.Properties, product...)
70+
71+
m.Licenses = []types.LicenseManagerLicenseInfo{EvalLicense}
72+
}
5573

5674
if r.IsVPX() {
5775
if m.LicenseAssignmentManager == nil {
@@ -60,9 +78,16 @@ func (m *LicenseManager) init(r *Registry) {
6078
Value: "LicenseAssignmentManager",
6179
}
6280
}
63-
r.Put(&LicenseAssignmentManager{
64-
mo.LicenseAssignmentManager{Self: *m.LicenseAssignmentManager},
65-
})
81+
82+
lam := new(LicenseAssignmentManager)
83+
lam.Self = *m.LicenseAssignmentManager
84+
lam.QueryAssignedLicensesResponse.Returnval = []types.LicenseAssignmentManagerLicenseAssignment{{
85+
EntityId: r.content().About.InstanceUuid,
86+
EntityDisplayName: "vcsim",
87+
AssignedLicense: EvalLicense,
88+
}}
89+
r.Put(lam)
90+
r.AddHandler(lam)
6691
}
6792
}
6893

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

130+
func (m *LicenseManager) DecodeLicense(ctx *Context, req *types.DecodeLicense) soap.HasFault {
131+
body := &methods.DecodeLicenseBody{
132+
Res: &types.DecodeLicenseResponse{},
133+
}
134+
135+
for _, license := range m.Licenses {
136+
if req.LicenseKey == license.LicenseKey {
137+
body.Res.Returnval = license
138+
break
139+
}
140+
}
141+
142+
return body
143+
}
144+
105145
func (m *LicenseManager) UpdateLicenseLabel(ctx *Context, req *types.UpdateLicenseLabel) soap.HasFault {
106146
body := &methods.UpdateLicenseLabelBody{}
107147

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

142182
type LicenseAssignmentManager struct {
143183
mo.LicenseAssignmentManager
184+
185+
types.QueryAssignedLicensesResponse
186+
}
187+
188+
var licensedTypes = []string{"HostSystem", "ClusterComputeResource"}
189+
190+
// PutObject assigns a license when a host or cluster is created.
191+
func (m *LicenseAssignmentManager) PutObject(obj mo.Reference) {
192+
ref := obj.Reference()
193+
194+
if !slices.Contains(licensedTypes, ref.Type) {
195+
return
196+
}
197+
198+
if slices.ContainsFunc(m.QueryAssignedLicensesResponse.Returnval,
199+
func(am types.LicenseAssignmentManagerLicenseAssignment) bool {
200+
return am.EntityId == ref.Value
201+
}) {
202+
return // via vcsim -load
203+
}
204+
205+
la := types.LicenseAssignmentManagerLicenseAssignment{
206+
EntityId: ref.Value,
207+
Scope: Map.content().About.InstanceUuid,
208+
EntityDisplayName: obj.(mo.Entity).Entity().Name,
209+
AssignedLicense: EvalLicense,
210+
}
211+
212+
m.QueryAssignedLicensesResponse.Returnval =
213+
append(m.QueryAssignedLicensesResponse.Returnval, la)
214+
}
215+
216+
// RemoveObject removes the license assignment when a host or cluster is removed.
217+
func (m *LicenseAssignmentManager) RemoveObject(ctx *Context, ref types.ManagedObjectReference) {
218+
if !slices.Contains(licensedTypes, ref.Type) {
219+
return
220+
}
221+
222+
m.QueryAssignedLicensesResponse.Returnval =
223+
slices.DeleteFunc(m.QueryAssignedLicensesResponse.Returnval,
224+
func(am types.LicenseAssignmentManagerLicenseAssignment) bool {
225+
return am.EntityId == ref.Value
226+
})
227+
}
228+
229+
func (*LicenseAssignmentManager) UpdateObject(*Context, mo.Reference, []types.PropertyChange) {}
230+
231+
func (m *LicenseAssignmentManager) init(r *Registry) {
232+
r.AddHandler(m)
144233
}
145234

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

151-
// EntityId can be a HostSystem or the vCenter InstanceUuid
152-
if req.EntityId != "" {
153-
if req.EntityId != ctx.Map.content().About.InstanceUuid {
154-
id := types.ManagedObjectReference{
155-
Type: "HostSystem",
156-
Value: req.EntityId,
157-
}
158-
159-
if ctx.Map.Get(id) == nil {
160-
return body
240+
if req.EntityId == "" {
241+
body.Res = &m.QueryAssignedLicensesResponse
242+
} else {
243+
for _, r := range m.QueryAssignedLicensesResponse.Returnval {
244+
if r.EntityId == req.EntityId {
245+
body.Res.Returnval = append(body.Res.Returnval, r)
161246
}
162247
}
163248
}
164249

165-
body.Res.Returnval = []types.LicenseAssignmentManagerLicenseAssignment{
166-
{
167-
EntityId: req.EntityId,
168-
AssignedLicense: EvalLicense,
169-
},
170-
}
171-
172250
return body
173251
}
174252

175253
func (m *LicenseAssignmentManager) UpdateAssignedLicense(ctx *Context, req *types.UpdateAssignedLicense) soap.HasFault {
176-
body := &methods.UpdateAssignedLicenseBody{
177-
Res: &types.UpdateAssignedLicenseResponse{
178-
Returnval: licenseInfo(req.LicenseKey, nil),
179-
},
254+
body := new(methods.UpdateAssignedLicenseBody)
255+
256+
var license *types.LicenseManagerLicenseInfo
257+
lm := ctx.Map.Get(*ctx.Map.content().LicenseManager).(*LicenseManager)
258+
259+
for i, l := range lm.Licenses {
260+
if l.LicenseKey == req.LicenseKey {
261+
license = &lm.Licenses[i]
262+
}
263+
}
264+
265+
if license == nil {
266+
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "entityId"})
267+
return body
268+
}
269+
270+
for i, r := range m.QueryAssignedLicensesResponse.Returnval {
271+
if r.EntityId == req.Entity {
272+
r.AssignedLicense = *license
273+
274+
if req.EntityDisplayName != "" {
275+
r.EntityDisplayName = req.EntityDisplayName
276+
}
277+
278+
m.QueryAssignedLicensesResponse.Returnval[i] = r
279+
280+
body.Res = &types.UpdateAssignedLicenseResponse{
281+
Returnval: r.AssignedLicense,
282+
}
283+
284+
break
285+
}
180286
}
181287

182288
return body

simulator/license_manager_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,13 @@ func TestLicenseManagerVPX(t *testing.T) {
7575
t.Fatal(err)
7676
}
7777

78-
if len(la) != 1 {
79-
t.Fatal("no licenses")
78+
expect := 1
79+
if name == "" {
80+
count := m.Count()
81+
expect = count.Host + count.ClusterHost + count.Cluster + 1 // (1 == vCenter)
82+
}
83+
if len(la) != expect {
84+
t.Fatalf("%d licenses", len(la))
8085
}
8186

8287
if !reflect.DeepEqual(la[0].AssignedLicense, EvalLicense) {

simulator/model.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ var kinds = map[string]reflect.Type{
257257
"HostSystem": reflect.TypeOf((*HostSystem)(nil)).Elem(),
258258
"IpPoolManager": reflect.TypeOf((*IpPoolManager)(nil)).Elem(),
259259
"LicenseManager": reflect.TypeOf((*LicenseManager)(nil)).Elem(),
260+
"LicenseAssignmentManager": reflect.TypeOf((*LicenseAssignmentManager)(nil)).Elem(),
260261
"OptionManager": reflect.TypeOf((*OptionManager)(nil)).Elem(),
261262
"OvfManager": reflect.TypeOf((*OvfManager)(nil)).Elem(),
262263
"PerformanceManager": reflect.TypeOf((*PerformanceManager)(nil)).Elem(),

0 commit comments

Comments
 (0)