github.com/vmware/govmomi@v0.37.1/vapi/namespace/simulator/simulator.go (about) 1 /* 2 Copyright (c) 2020-2022 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 "archive/tar" 21 "context" 22 "encoding/json" 23 "fmt" 24 "net/http" 25 "net/url" 26 "path" 27 "strings" 28 "time" 29 30 "github.com/google/uuid" 31 32 "github.com/vmware/govmomi" 33 "github.com/vmware/govmomi/property" 34 "github.com/vmware/govmomi/simulator" 35 "github.com/vmware/govmomi/vapi/namespace" 36 vapi "github.com/vmware/govmomi/vapi/simulator" 37 "github.com/vmware/govmomi/view" 38 "github.com/vmware/govmomi/vim25/types" 39 40 "github.com/vmware/govmomi/vapi/namespace/internal" 41 ) 42 43 func init() { 44 simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) { 45 New(s.Listen).Register(s, r) 46 }) 47 } 48 49 // Handler implements the Cluster Modules API simulator 50 type Handler struct { 51 URL *url.URL 52 } 53 54 // New creates a Handler instance 55 func New(u *url.URL) *Handler { 56 return &Handler{ 57 URL: u, 58 } 59 } 60 61 // Register Namespace Management API paths with the vapi simulator's http.ServeMux 62 func (h *Handler) Register(s *simulator.Service, r *simulator.Registry) { 63 if r.IsVPX() { 64 s.HandleFunc(internal.NamespacesPath, h.namespaces) 65 s.HandleFunc(internal.NamespacesPath+"/", h.namespaces) 66 s.HandleFunc(internal.NamespaceClusterPath, h.clusters) 67 s.HandleFunc(internal.NamespaceClusterPath+"/", h.clustersID) 68 s.HandleFunc(internal.NamespaceDistributedSwitchCompatibility+"/", h.listCompatibleDistributedSwitches) 69 s.HandleFunc(internal.NamespaceEdgeClusterCompatibility+"/", h.listCompatibleEdgeClusters) 70 71 s.HandleFunc(internal.SupervisorServicesPath, h.listServices) 72 s.HandleFunc(internal.SupervisorServicesPath+"/", h.getService) 73 74 s.HandleFunc(internal.VmClassesPath, h.vmClasses) 75 s.HandleFunc(internal.VmClassesPath+"/", h.vmClasses) 76 } 77 } 78 79 // enabledClusters returns refs for cluster names with a "WCP-" prefix. 80 // Using the name as a simple hack until we add support for enabling via the API. 81 func enabledClusters(c *govmomi.Client) ([]types.ManagedObjectReference, error) { 82 ctx := context.Background() 83 kind := []string{"ClusterComputeResource"} 84 85 m := view.NewManager(c.Client) 86 v, err := m.CreateContainerView(ctx, c.ServiceContent.RootFolder, kind, true) 87 if err != nil { 88 return nil, err 89 } 90 defer func() { _ = v.Destroy(ctx) }() 91 92 return v.Find(ctx, kind, property.Match{"name": "WCP-*"}) 93 } 94 95 func (h *Handler) clusters(w http.ResponseWriter, r *http.Request) { 96 c, err := govmomi.NewClient(context.Background(), h.URL, true) 97 if err != nil { 98 panic(err) 99 } 100 101 switch r.Method { 102 case http.MethodGet: 103 refs, err := enabledClusters(c) 104 if err != nil { 105 panic(err) 106 } 107 108 clusters := make([]namespace.ClusterSummary, len(refs)) 109 for i, ref := range refs { 110 clusters[i] = namespace.ClusterSummary{ 111 ID: ref.Value, 112 ConfigStatus: &namespace.RunningConfigStatus, 113 KubernetesStatus: &namespace.ReadyKubernetesStatus, 114 } 115 } 116 vapi.StatusOK(w, clusters) 117 } 118 } 119 120 func (h *Handler) clustersSupportBundle(w http.ResponseWriter, r *http.Request) { 121 var token internal.SupportBundleToken 122 _ = json.NewDecoder(r.Body).Decode(&token) 123 _ = r.Body.Close() 124 125 if token.Value == "" { 126 u := *h.URL 127 u.Path = r.URL.Path 128 // Create support bundle request 129 location := namespace.SupportBundleLocation{ 130 Token: namespace.SupportBundleToken{ 131 Token: uuid.New().String(), 132 }, 133 URL: u.String(), 134 } 135 136 vapi.StatusOK(w, &location) 137 return 138 } 139 140 // Get support bundle 141 id := path.Base(path.Dir(r.URL.Path)) 142 name := fmt.Sprintf("wcp-support-bundle-%s-%s--00-00.tar", id, time.Now().Format("2006Jan02")) 143 144 w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name)) 145 w.Header().Set("Content-Type", "application/octet-stream") 146 147 readme := "vcsim generated support bundle" 148 tw := tar.NewWriter(w) 149 _ = tw.WriteHeader(&tar.Header{ 150 Name: "README", 151 Size: int64(len(readme) + 1), 152 Mode: 0444, 153 ModTime: time.Now(), 154 }) 155 _, _ = fmt.Fprintln(tw, readme) 156 _ = tw.Close() 157 } 158 159 func (h *Handler) clustersID(w http.ResponseWriter, r *http.Request) { 160 id := path.Base(r.URL.Path) 161 route := map[string]func(http.ResponseWriter, *http.Request){ 162 "support-bundle": h.clustersSupportBundle, 163 }[id] 164 165 if route != nil { 166 route(w, r) 167 return 168 } 169 170 // TODO: 171 // https://vmware.github.io/vsphere-automation-sdk-rest/vsphere/index.html#SVC_com.vmware.vcenter.namespace_management.clusters 172 } 173 174 func (h *Handler) listCompatibleDistributedSwitches(w http.ResponseWriter, r *http.Request) { 175 switch r.Method { 176 case http.MethodGet: 177 178 // normally expect to get exactly one result back 179 switches := []namespace.DistributedSwitchCompatibilitySummary{ 180 { 181 Compatible: true, 182 DistributedSwitch: "Compatible-DVS-1", 183 }, 184 } 185 vapi.StatusOK(w, switches) 186 } 187 } 188 189 func (h *Handler) listCompatibleEdgeClusters(w http.ResponseWriter, r *http.Request) { 190 switch r.Method { 191 case http.MethodGet: 192 193 // CLI is able to filter in case we get multiple results 194 switches := []namespace.EdgeClusterCompatibilitySummary{ 195 { 196 Compatible: true, 197 EdgeCluster: "Compat-Edge-ID1", 198 DisplayName: "Edge-Cluster-1", 199 }, 200 { 201 Compatible: true, 202 EdgeCluster: "Compat-Edge-ID2", 203 DisplayName: "Edge-Cluster-2", 204 }, 205 } 206 vapi.StatusOK(w, switches) 207 } 208 } 209 210 var supervisorServices []namespace.SupervisorServiceSummary = []namespace.SupervisorServiceSummary{ 211 { 212 ID: "service1", 213 Name: "mock-service-1", 214 State: "ACTIVATED", 215 }, 216 { 217 ID: "service2", 218 Name: "mock-service-2", 219 State: "DE-ACTIVATED", 220 }, 221 } 222 223 func (h *Handler) listServices(w http.ResponseWriter, r *http.Request) { 224 switch r.Method { 225 case http.MethodGet: 226 vapi.StatusOK(w, supervisorServices) 227 } 228 } 229 230 func (h *Handler) getService(w http.ResponseWriter, r *http.Request) { 231 id := path.Base(r.URL.Path) 232 switch r.Method { 233 case http.MethodGet: 234 for _, svc := range supervisorServices { 235 if svc.ID == id { 236 svcInfo := namespace.SupervisorServiceInfo{ 237 Name: svc.Name, 238 State: svc.State, 239 Description: fmt.Sprintf("Description of %s", svc.ID), 240 } 241 vapi.StatusOK(w, svcInfo) 242 return 243 } 244 } 245 w.WriteHeader(http.StatusNotFound) 246 } 247 } 248 249 var namespacesMap = make(map[string]*namespace.NamespacesInstanceInfo) 250 251 func (h *Handler) namespaces(w http.ResponseWriter, r *http.Request) { 252 subpath := r.URL.Path[len(internal.NamespacesPath):] 253 subpath = strings.Replace(subpath, "/", "", -1) 254 255 switch r.Method { 256 case http.MethodGet: 257 if len(subpath) > 0 { 258 if result, contains := namespacesMap[subpath]; contains { 259 vapi.StatusOK(w, result) 260 } else { 261 vapi.ApiErrorNotFound(w) 262 } 263 return 264 } else { 265 result := make([]namespace.NamespacesInstanceSummary, 0, len(namespacesMap)) 266 267 for k, v := range namespacesMap { 268 entry := namespace.NamespacesInstanceSummary{ 269 ClusterId: v.ClusterId, 270 Namespace: k, 271 ConfigStatus: v.ConfigStatus, 272 Description: v.Description, 273 Stats: v.Stats, 274 } 275 result = append(result, entry) 276 } 277 278 vapi.StatusOK(w, result) 279 } 280 case http.MethodPatch: 281 if len(subpath) > 0 { 282 if entry, contains := namespacesMap[subpath]; contains { 283 var spec namespace.NamespacesInstanceUpdateSpec 284 if vapi.Decode(r, w, &spec) { 285 entry.VmServiceSpec = spec.VmServiceSpec 286 vapi.StatusOK(w) 287 } 288 } 289 } 290 291 vapi.ApiErrorNotFound(w) 292 case http.MethodPost: 293 var spec namespace.NamespacesInstanceCreateSpec 294 if !vapi.Decode(r, w, &spec) { 295 return 296 } 297 298 newNamespace := namespace.NamespacesInstanceInfo{ 299 ClusterId: spec.Cluster, 300 ConfigStatus: namespace.RunningConfigStatus.String(), 301 VmServiceSpec: spec.VmServiceSpec, 302 } 303 304 namespacesMap[spec.Namespace] = &newNamespace 305 306 vapi.StatusOK(w) 307 case http.MethodDelete: 308 if len(subpath) > 0 { 309 if _, contains := namespacesMap[subpath]; contains { 310 delete(namespacesMap, subpath) 311 vapi.StatusOK(w) 312 return 313 } 314 } 315 vapi.ApiErrorNotFound(w) 316 } 317 } 318 319 var vmClassesMap = make(map[string]*namespace.VirtualMachineClassInfo) 320 321 func (h *Handler) vmClasses(w http.ResponseWriter, r *http.Request) { 322 subpath := r.URL.Path[len(internal.VmClassesPath):] 323 subpath = strings.Replace(subpath, "/", "", -1) 324 325 switch r.Method { 326 case http.MethodGet: 327 if len(subpath) > 0 { 328 if result, contains := vmClassesMap[subpath]; contains { 329 vapi.StatusOK(w, result) 330 } else { 331 vapi.ApiErrorNotFound(w) 332 } 333 return 334 } else { 335 result := make([]*namespace.VirtualMachineClassInfo, 0, len(vmClassesMap)) 336 337 for _, v := range vmClassesMap { 338 result = append(result, v) 339 } 340 341 vapi.StatusOK(w, result) 342 } 343 case http.MethodPatch: 344 if len(subpath) > 0 { 345 if entry, contains := vmClassesMap[subpath]; contains { 346 var spec namespace.VirtualMachineClassUpdateSpec 347 if !vapi.Decode(r, w, &spec) { 348 return 349 } 350 351 entry.CpuCount = spec.CpuCount 352 entry.MemoryMb = spec.MemoryMb 353 entry.CpuReservation = spec.CpuReservation 354 entry.MemoryReservation = spec.MemoryReservation 355 entry.Devices = spec.Devices 356 357 vapi.StatusOK(w) 358 return 359 } 360 } 361 362 vapi.ApiErrorNotFound(w) 363 case http.MethodPost: 364 var spec namespace.VirtualMachineClassCreateSpec 365 if !vapi.Decode(r, w, &spec) { 366 return 367 } 368 369 newClass := namespace.VirtualMachineClassInfo{ 370 Id: spec.Id, 371 CpuCount: spec.CpuCount, 372 MemoryMb: spec.MemoryMb, 373 MemoryReservation: spec.MemoryReservation, 374 CpuReservation: spec.CpuReservation, 375 Devices: spec.Devices, 376 } 377 378 vmClassesMap[spec.Id] = &newClass 379 380 vapi.StatusOK(w) 381 case http.MethodDelete: 382 if len(subpath) > 0 { 383 if _, contains := vmClassesMap[subpath]; contains { 384 delete(vmClassesMap, subpath) 385 vapi.StatusOK(w) 386 return 387 } 388 } 389 vapi.ApiErrorNotFound(w) 390 } 391 }