From f67104896a2c938159f288911b99004049a38de6 Mon Sep 17 00:00:00 2001 From: Sreeja Acharya Date: Mon, 8 Apr 2024 11:52:32 -0700 Subject: [PATCH 1/3] api: Expose vSphere API to evict subscribed content library Closes: #3400 Signed-off-by: Sreeja Acharya --- vapi/library/library.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vapi/library/library.go b/vapi/library/library.go index 62ad873bc..c296a62aa 100644 --- a/vapi/library/library.go +++ b/vapi/library/library.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -322,3 +322,11 @@ func (c *Manager) DeleteSubscriber(ctx context.Context, library *Library, subscr url := c.Resource(internal.Subscriptions).WithID(library.ID).WithAction("delete") return c.Do(ctx, url.Request(http.MethodPost, &id), nil) } + +// EvictSubscribedLibrary evicts the cached content of an on-demand subscribed library. +// This operation allows the cached content of a subscribed library to be removed to free up storage capacity. +func (c *Manager) EvictSubscribedLibrary(ctx context.Context, library *Library) error { + path := internal.SubscribedLibraryPath + url := c.Resource(path).WithID(library.ID).WithAction("evict") + return c.Do(ctx, url.Request(http.MethodPost), nil) +} From 9285e4412fb485183daa86ff8da50a74c13be9c1 Mon Sep 17 00:00:00 2001 From: Sreeja Acharya Date: Fri, 12 Apr 2024 10:14:17 -0700 Subject: [PATCH 2/3] govc: Add library.evict command --- govc/library/evict.go | 79 ++++++++++++++++++++++++++++++++++++ govc/library/info.go | 1 + govc/test/library.bats | 44 ++++++++++++++++++++ vapi/library/library_item.go | 8 ++++ 4 files changed, 132 insertions(+) create mode 100644 govc/library/evict.go diff --git a/govc/library/evict.go b/govc/library/evict.go new file mode 100644 index 000000000..e676200bd --- /dev/null +++ b/govc/library/evict.go @@ -0,0 +1,79 @@ +/* +Copyright (c) 2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package library + +import ( + "context" + "flag" + "fmt" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vapi/library" +) + +type evict struct { + *flags.ClientFlag +} + +func init() { + cli.Register("library.evict", &evict{}) +} + +func (cmd *evict) Register(ctx context.Context, f *flag.FlagSet) { + cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) + cmd.ClientFlag.Register(ctx, f) +} + +func (cmd *evict) Usage() string { + return "LIBRARY NAME | ITEM NAME" +} + +func (cmd *evict) Description() string { + return `Evict library NAME or item NAME. + +Examples: + govc library.evict subscribed-library + govc library.evict subscribed-library/item` +} + +func (cmd *evict) Run(ctx context.Context, f *flag.FlagSet) error { + if f.NArg() != 1 { + return flag.ErrHelp + } + + c, err := cmd.RestClient() + if err != nil { + return err + } + + m := library.NewManager(c) + + res, err := flags.ContentLibraryResult(ctx, c, "", f.Arg(0)) + if err != nil { + return err + } + + switch t := res.GetResult().(type) { + case library.Library: + return m.EvictSubscribedLibrary(ctx, &t) + case library.Item: + return m.EvictSubscribedLibraryItem(ctx, &t) + default: + return fmt.Errorf("%q is a %T", f.Arg(0), t) + } +} diff --git a/govc/library/info.go b/govc/library/info.go index 07233313c..87bd6d073 100644 --- a/govc/library/info.go +++ b/govc/library/info.go @@ -223,6 +223,7 @@ func (r infoResultsWriter) writeItem( } fmt.Fprintf(w, " Type:\t%s\n", v.Type) fmt.Fprintf(w, " Size:\t%s\n", units.ByteSize(v.Size)) + fmt.Fprintf(w, " Cached:\t%t\n", v.Cached) fmt.Fprintf(w, " Created:\t%s\n", v.CreationTime.Format(time.ANSIC)) fmt.Fprintf(w, " Modified:\t%s\n", v.LastModifiedTime.Format(time.ANSIC)) fmt.Fprintf(w, " Version:\t%s\n", v.Version) diff --git a/govc/test/library.bats b/govc/test/library.bats index cdb064d7d..eff1b415d 100755 --- a/govc/test/library.bats +++ b/govc/test/library.bats @@ -607,3 +607,47 @@ EOF assert_success assert_matches INVALID_URL } + +@test "library.sublibevict" { + vcsim_env + + run govc library.create -pub published-content + assert_success + id="$output" + + url="https://$(govc env GOVC_URL)/cls/vcsp/lib/$id" + + run govc library.info published-content + assert_success + assert_matches "Publication:" + assert_matches "$url" + + run govc library.import published-content "$GOVC_IMAGES/ttylinux-latest.ova" + assert_success + + run govc library.create -sub "$url" -sub-ondemand=true subscribed-content + assert_success + + run govc library.info subscribed-content + assert_success + assert_matches "Subscription:" + assert_matches "$url" + + run govc library.ls subscribed-content/ttylinux-latest/ + assert_success + assert_matches "/subscribed-content/ttylinux-latest/ttylinux-pc_i486-16.1.ovf" + + run govc library.sync subscribed-content/ttylinux-latest + assert_success + + result=$(govc library.info -l subscribed-content/ttylinux-latest) + echo "$result" + # assert cached is true + + run govc library.evict subscribed-content + assert_success + + # assert cached is false +} + + diff --git a/vapi/library/library_item.go b/vapi/library/library_item.go index e143da5d9..02e16535d 100644 --- a/vapi/library/library_item.go +++ b/vapi/library/library_item.go @@ -206,3 +206,11 @@ func (c *Manager) FindLibraryItems( var res []string return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res) } + +// EvictSubscribedLibraryItem evicts the cached content of a library item in an on-demand subscribed library. +// This operation allows the cached content of a subscribed library item to be removed to free up storage capacity. +func (c *Manager) EvictSubscribedLibraryItem(ctx context.Context, item *Item) error { + path := internal.SubscribedLibraryItem + url := c.Resource(path).WithID(item.ID).WithAction("evict") + return c.Do(ctx, url.Request(http.MethodPost), nil) +} From 70a7617dbc44666488e7c3c7aababae1fc703b6e Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Fri, 12 Apr 2024 11:30:43 -0700 Subject: [PATCH 3/3] vcsim: toggle Content Library cached fields in sync and evict calls --- govc/USAGE.md | 15 +++++++++++++++ govc/library/evict.go | 2 +- govc/test/library.bats | 14 ++++++++------ vapi/simulator/simulator.go | 24 +++++++++++++++++++++++- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/govc/USAGE.md b/govc/USAGE.md index 36486ab63..add6b3532 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -217,6 +217,7 @@ but appear via `govc $cmd -h`: - [library.cp](#librarycp) - [library.create](#librarycreate) - [library.deploy](#librarydeploy) + - [library.evict](#libraryevict) - [library.export](#libraryexport) - [library.import](#libraryimport) - [library.info](#libraryinfo) @@ -3549,6 +3550,20 @@ Options: -profile= Storage profile ``` +## library.evict + +``` +Usage: govc library.evict [OPTIONS] LIBRARY NAME | ITEM NAME + +Evict library NAME or item NAME. + +Examples: + govc library.evict subscribed-library + govc library.evict subscribed-library/item + +Options: +``` + ## library.export ``` diff --git a/govc/library/evict.go b/govc/library/evict.go index e676200bd..daf5907aa 100644 --- a/govc/library/evict.go +++ b/govc/library/evict.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2024 VMware, Inc. All Rights Reserved. +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/govc/test/library.bats b/govc/test/library.bats index eff1b415d..695c21c32 100755 --- a/govc/test/library.bats +++ b/govc/test/library.bats @@ -608,7 +608,7 @@ EOF assert_matches INVALID_URL } -@test "library.sublibevict" { +@test "library.evict" { vcsim_env run govc library.create -pub published-content @@ -640,14 +640,16 @@ EOF run govc library.sync subscribed-content/ttylinux-latest assert_success - result=$(govc library.info -l subscribed-content/ttylinux-latest) - echo "$result" - # assert cached is true + # assert cached is false after item sync + cached=$(govc library.info subscribed-content/ttylinux-latest | grep Cached: | awk '{print $2}') + assert_equal "true" "$cached" - run govc library.evict subscribed-content + run govc library.evict subscribed-content/ttylinux-latest assert_success - # assert cached is false + # assert cached is false after library item evict + cached=$(govc library.info subscribed-content/ttylinux-latest | grep Cached: | awk '{print $2}') + assert_equal "false" "$cached" } diff --git a/vapi/simulator/simulator.go b/vapi/simulator/simulator.go index af1ef9df5..b1d23189f 100644 --- a/vapi/simulator/simulator.go +++ b/vapi/simulator/simulator.go @@ -905,6 +905,19 @@ func (s *handler) library(w http.ResponseWriter, r *http.Request) { } } +func (content *content) cached(val bool) { + for _, item := range content.Item { + item.cached(val) + } +} + +func (item *item) cached(val bool) { + item.Cached = val + for _, file := range item.File { + file.Cached = types.NewBool(val) + } +} + func (s *handler) publish(w http.ResponseWriter, r *http.Request, sids []internal.SubscriptionDestination, l *content, vmtx *item) bool { var ids []string if len(sids) == 0 { @@ -991,10 +1004,14 @@ func (s *handler) libraryID(w http.ResponseWriter, r *http.Request) { case "sync": if l.Type == "SUBSCRIBED" { l.LastSyncTime = types.NewTime(time.Now()) + l.cached(true) OK(w) } else { http.NotFound(w, r) } + case "evict": + l.cached(false) + OK(w) } case http.MethodGet: OK(w, l) @@ -1242,8 +1259,9 @@ func (s *handler) libraryItemID(w http.ResponseWriter, r *http.Request) { OK(w, id) case "sync": - if l.Type == "SUBSCRIBED" { + if l.Type == "SUBSCRIBED" || l.Publication != nil { item.LastSyncTime = types.NewTime(time.Now()) + item.cached(true) OK(w) } else { http.NotFound(w, r) @@ -1255,6 +1273,9 @@ func (s *handler) libraryItemID(w http.ResponseWriter, r *http.Request) { OK(w) } } + case "evict": + item.cached(false) + OK(w, id) } case http.MethodGet: OK(w, item) @@ -2309,6 +2330,7 @@ func (s *handler) libraryItemTemplateID(w http.ResponseWriter, r *http.Request) return } + item.cached(true) ref, err := s.cloneVM(item.Template.Value, spec.Name, p, spec.DiskStorage) if err != nil { BadRequest(w, err.Error())