github.com/vmware/govmomi@v0.43.0/vapi/esx/settings/simulator/simulator.go (about) 1 /* 2 Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package simulator 18 19 import ( 20 "fmt" 21 "net/http" 22 "net/url" 23 "strings" 24 25 "github.com/vmware/govmomi/simulator" 26 "github.com/vmware/govmomi/vapi/esx/settings/clusters" 27 "github.com/vmware/govmomi/vapi/esx/settings/depots" 28 vapi "github.com/vmware/govmomi/vapi/simulator" 29 ) 30 31 func init() { 32 simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) { 33 New(s.Listen).Register(s, r) 34 }) 35 } 36 37 type Handler struct { 38 URL *url.URL 39 40 Depots map[string]depots.SettingsDepotsOfflineInfo 41 DepotComponents map[string]depots.SettingsDepotsOfflineContentInfo 42 BaseImages []depots.BaseImagesSummary 43 44 SoftwareDrafts map[string]clusters.SettingsClustersSoftwareDraftsMetadata 45 SoftwareComponents map[string]clusters.SettingsComponentInfo 46 ClusterImage *clusters.SettingsBaseImageInfo 47 48 depotCounter int 49 draftCounter int 50 51 vlcmEnabled bool 52 } 53 54 // New creates a Handler instance 55 func New(u *url.URL) *Handler { 56 return &Handler{ 57 URL: u, 58 Depots: make(map[string]depots.SettingsDepotsOfflineInfo), 59 DepotComponents: make(map[string]depots.SettingsDepotsOfflineContentInfo), 60 BaseImages: createMockBaseImages(), 61 SoftwareDrafts: make(map[string]clusters.SettingsClustersSoftwareDraftsMetadata), 62 SoftwareComponents: make(map[string]clusters.SettingsComponentInfo), 63 depotCounter: 0, 64 vlcmEnabled: false, 65 } 66 } 67 68 func (h *Handler) Register(s *simulator.Service, r *simulator.Registry) { 69 if r.IsVPX() { 70 s.HandleFunc(depots.DepotsOfflinePath, h.depotsOffline) 71 s.HandleFunc(depots.DepotsOfflinePath+"/", h.depotsOffline) 72 s.HandleFunc(depots.BaseImagesPath, h.baseImages) 73 s.HandleFunc("/api/esx/settings/clusters/", h.clusters) 74 } 75 } 76 77 func (h *Handler) depotsOffline(w http.ResponseWriter, r *http.Request) { 78 subpath := r.URL.Path[len(depots.DepotsOfflinePath):] 79 segments := strings.Split(subpath, "/") 80 81 switch r.Method { 82 case http.MethodGet: 83 if len(segments) > 1 { 84 if res, contains := h.DepotComponents[segments[1]]; !contains { 85 vapi.ApiErrorNotFound(w) 86 } else if len(segments) > 2 && segments[2] == "content" { 87 vapi.StatusOK(w, res) 88 } else { 89 vapi.ApiErrorUnsupported(w) 90 } 91 } 92 vapi.StatusOK(w, h.Depots) 93 case http.MethodDelete: 94 if _, contains := h.Depots[segments[1]]; !contains { 95 vapi.ApiErrorNotFound(w) 96 } else { 97 delete(h.Depots, segments[1]) 98 delete(h.DepotComponents, segments[1]) 99 vapi.StatusOK(w, "") 100 } 101 case http.MethodPost: 102 var spec depots.SettingsDepotsOfflineCreateSpec 103 if vapi.Decode(r, w, &spec) { 104 // Create depot 105 depot := depots.SettingsDepotsOfflineInfo{} 106 depot.SourceType = spec.SourceType 107 depot.FileId = spec.FileId 108 depot.Location = spec.Location 109 depot.OwnerData = spec.OwnerData 110 depot.Description = spec.Description 111 112 h.depotCounter += 1 113 depotId := fmt.Sprintf("depot-%d", h.depotCounter) 114 h.Depots[depotId] = depot 115 116 // Generate content 117 content := depots.SettingsDepotsOfflineContentInfo{} 118 content.MetadataBundles = make(map[string][]depots.SettingsDepotsMetadataInfo) 119 content.MetadataBundles["dummy-content"] = make([]depots.SettingsDepotsMetadataInfo, 1) 120 bundle := depots.SettingsDepotsMetadataInfo{} 121 bundle.IndependentComponents = make(map[string]depots.SettingsDepotsComponentSummary) 122 independentComp := depots.SettingsDepotsComponentSummary{} 123 independentComp.Versions = make([]depots.ComponentVersion, 1) 124 independentComp.Versions[0].Version = "1.0.0" 125 independentComp.Versions[0].DisplayVersion = "1.0.0" 126 independentComp.DisplayName = "DummyComponent" 127 bundle.IndependentComponents["dummy-component"] = independentComp 128 content.MetadataBundles["dummy-content"][0] = bundle 129 h.DepotComponents[depotId] = content 130 131 vapi.StatusOK(w, fmt.Sprintf("depot-task-%d", h.depotCounter)) 132 } 133 } 134 } 135 136 func (h *Handler) baseImages(w http.ResponseWriter, r *http.Request) { 137 switch r.Method { 138 case http.MethodGet: 139 vapi.StatusOK(w, h.BaseImages) 140 } 141 } 142 143 func (h *Handler) clusters(w http.ResponseWriter, r *http.Request) { 144 subpath := r.URL.Path[len("/api/esx/settings/clusters"):] 145 segments := strings.Split(subpath, "/") 146 147 if len(segments) > 3 && segments[2] == "software" && segments[3] == "drafts" { 148 segments = segments[4:] 149 if len(segments) > 2 && segments[1] == "software" && segments[2] == "components" { 150 h.clustersSoftwareDraftsComponents(w, r, segments) 151 return 152 } else if len(segments) > 2 && segments[1] == "software" && segments[2] == "base-image" { 153 h.clustersSoftwareDraftsBaseImage(w, r) 154 return 155 } else { 156 h.clustersSoftwareDrafts(w, r, segments) 157 return 158 } 159 } else if len(segments) > 3 && segments[2] == "enablement" && segments[3] == "software" { 160 h.clustersSoftwareEnablement(w, r) 161 return 162 } 163 164 vapi.ApiErrorUnsupported(w) 165 } 166 167 func (h *Handler) clustersSoftwareDrafts(w http.ResponseWriter, r *http.Request, subpath []string) { 168 var draftId *string 169 if len(subpath) > 0 { 170 draftId = &subpath[0] 171 } 172 173 switch r.Method { 174 case http.MethodGet: 175 if draftId != nil { 176 if draft, contains := h.SoftwareDrafts[*draftId]; !contains { 177 vapi.ApiErrorNotFound(w) 178 return 179 } else { 180 vapi.StatusOK(w, draft) 181 } 182 } else { 183 vapi.StatusOK(w, h.SoftwareDrafts) 184 } 185 case http.MethodDelete: 186 if draftId != nil { 187 if _, contains := h.SoftwareDrafts[*draftId]; !contains { 188 vapi.ApiErrorNotFound(w) 189 return 190 } else { 191 delete(h.SoftwareDrafts, *draftId) 192 vapi.StatusOK(w) 193 } 194 } 195 case http.MethodPost: 196 if strings.Contains(r.URL.RawQuery, "action=commit") { 197 if draftId != nil { 198 if _, contains := h.SoftwareDrafts[*draftId]; !contains { 199 vapi.ApiErrorNotFound(w) 200 return 201 } else { 202 delete(h.SoftwareDrafts, *draftId) 203 vapi.StatusOK(w) 204 } 205 } 206 } 207 // Only one active draft is permitted 208 if len(h.SoftwareDrafts) > 0 { 209 vapi.ApiErrorNotAllowedInCurrentState(w) 210 return 211 } 212 213 h.draftCounter += 1 214 draft := clusters.SettingsClustersSoftwareDraftsMetadata{} 215 newDraftId := fmt.Sprintf("%d", h.draftCounter) 216 h.SoftwareDrafts[newDraftId] = draft 217 vapi.StatusOK(w, newDraftId) 218 } 219 } 220 221 func (h *Handler) clustersSoftwareDraftsComponents(w http.ResponseWriter, r *http.Request, subpath []string) { 222 switch r.Method { 223 case http.MethodGet: 224 if len(subpath) > 3 { 225 if comp, contains := h.SoftwareComponents[subpath[3]]; contains { 226 vapi.StatusOK(w, comp) 227 } else { 228 vapi.ApiErrorNotFound(w) 229 } 230 } else { 231 vapi.StatusOK(w, h.SoftwareComponents) 232 } 233 case http.MethodDelete: 234 if len(subpath) > 3 { 235 compId := subpath[3] 236 if comp, contains := h.SoftwareComponents[compId]; contains { 237 delete(h.SoftwareComponents, compId) 238 vapi.StatusOK(w, comp) 239 } else { 240 vapi.ApiErrorNotFound(w) 241 } 242 } 243 case http.MethodPatch: 244 var spec clusters.SoftwareComponentsUpdateSpec 245 if vapi.Decode(r, w, &spec) { 246 for k, v := range spec.ComponentsToSet { 247 h.SoftwareComponents[k] = clusters.SettingsComponentInfo{ 248 Version: v, 249 Details: clusters.SettingsComponentDetails{DisplayName: "DummyComponent"}, 250 } 251 } 252 } 253 } 254 } 255 256 func (h *Handler) clustersSoftwareDraftsBaseImage(w http.ResponseWriter, r *http.Request) { 257 switch r.Method { 258 case http.MethodGet: 259 vapi.StatusOK(w, h.ClusterImage) 260 case http.MethodPut: 261 var spec clusters.SettingsBaseImageSpec 262 if vapi.Decode(r, w, &spec) { 263 h.ClusterImage = &clusters.SettingsBaseImageInfo{Version: spec.Version} 264 vapi.StatusOK(w) 265 } else { 266 vapi.ApiErrorInvalidArgument(w) 267 } 268 } 269 } 270 271 func (h *Handler) clustersSoftwareEnablement(w http.ResponseWriter, r *http.Request) { 272 switch r.Method { 273 case http.MethodGet: 274 vapi.StatusOK(w, clusters.SoftwareManagementInfo{Enabled: h.vlcmEnabled}) 275 case http.MethodPut: 276 h.vlcmEnabled = true 277 vapi.StatusOK(w) 278 } 279 } 280 281 func createMockBaseImages() []depots.BaseImagesSummary { 282 baseImage := depots.BaseImagesSummary{ 283 DisplayName: "DummyImage", 284 DisplayVersion: "0.0.1", 285 Version: "0.0.1", 286 } 287 return []depots.BaseImagesSummary{baseImage} 288 }