go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/appengine/model/model.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package model contains datastore model definitions. 16 package model 17 18 import ( 19 "fmt" 20 "strings" 21 22 computealpha "google.golang.org/api/compute/v0.alpha" 23 compute "google.golang.org/api/compute/v1" 24 25 "go.chromium.org/luci/gae/service/datastore" 26 "go.chromium.org/luci/gce/api/config/v1" 27 "go.chromium.org/luci/gce/api/projects/v1" 28 "go.chromium.org/luci/gce/appengine/convert/toalpha" 29 ) 30 31 // ConfigKind is a config entity's kind in the datastore. 32 const ConfigKind = "Config" 33 34 // Config is a root entity representing a config for one type of VMs. 35 // VM entities should be created for each config entity. 36 type Config struct { 37 // _extra is where unknown properties are put into memory. 38 // Extra properties are not written to the datastore. 39 _extra datastore.PropertyMap `gae:"-,extra"` 40 // _kind is the entity's kind in the datastore. 41 _kind string `gae:"$kind,Config"` 42 // ID is the unique identifier for this config. 43 ID string `gae:"$id"` 44 // Config is the config.Config representation of this entity. 45 // Indexing is not useful here since this field contains textproto. 46 // Additionally, indexed string fields are limited to 1500 bytes. 47 // https://cloud.google.com/datastore/docs/concepts/limits. 48 // noindex is not respected here. See config.Config.ToProperty. 49 Config *config.Config `gae:"binary_config,legacy"` 50 } 51 52 // ProjectKind is a project entity's kind in the datastore. 53 const ProjectKind = "Project" 54 55 // Project is a root entity representing a GCP project. 56 // GCE quota utilization is reported for each metric in each region. 57 type Project struct { 58 // _extra is where unknown properties are put into memory. 59 // Extra properties are not written to the datastore. 60 _extra datastore.PropertyMap `gae:"-,extra"` 61 // _kind is the entity's kind in the datastore. 62 _kind string `gae:"$kind,Project"` 63 // ID is the unique identifier for this project. 64 ID string `gae:"$id"` 65 // Config is the projects.Config representation of this entity. 66 // noindex is not respected here. See projects.Config.ToProperty. 67 Config *projects.Config `gae:"binary_config,legacy"` 68 } 69 70 // VMKind is a VM entity's kind in the datastore. 71 const VMKind = "VM" 72 73 // NetworkInterface is a network interface attached to a GCE instance. 74 type NetworkInterface struct { 75 // ExternalIP is an external network address assigned to a GCE instance. 76 // GCE currently supports at most one external IP address per network 77 // interface. 78 ExternalIP string 79 // Internal is an internal network address assigned to a GCE instance. 80 InternalIP string 81 } 82 83 // VM is a root entity representing a configured VM. 84 // GCE instances should be created for each VM entity. 85 type VM struct { 86 // _extra is where unknown properties are put into memory. 87 // Extra properties are not written to the datastore. 88 _extra datastore.PropertyMap `gae:"-,extra"` 89 // _kind is the entity's kind in the datastore. 90 _kind string `gae:"$kind,VM"` 91 // ID is the unique identifier for this VM. 92 ID string `gae:"$id"` 93 // Attributes is the config.VM describing the GCE instance to create. 94 // Indexing is not useful here since this field contains textproto. 95 // noindex is not respected here. See config.VM.ToProperty. 96 Attributes config.VM `gae:"binary_attributes,noindex"` 97 // AttributesIndexed is a slice of strings in "key:value" form where the key is 98 // the path to a field in Attributes and the value is its associated value. 99 // Allows fields from Attributes to be indexed. 100 AttributesIndexed []string `gae:"attributes_indexed"` 101 // Config is the ID of the config this VM was created from. 102 Config string `gae:"config"` 103 // Configured is the Unix time when the GCE instance was configured. 104 Configured int64 `gae:"configured"` 105 // Connected is the Unix time when the GCE instance connected to Swarming. 106 Connected int64 `gae:"connected"` 107 // Created is the Unix time when the GCE instance was created. 108 Created int64 `gae:"created"` 109 // Drained indicates whether or not this VM is drained. 110 // A GCE instance should not be created for a drained VM. 111 // Any existing GCE instance should be deleted regardless of deadline. 112 Drained bool `gae:"drained"` 113 // DUT is the name of the matched lab DUT. 114 // Optional if the VM is not linked to a DUT. 115 DUT string `gae:"dut"` 116 // Hostname is the short hostname of the GCE instance to create. 117 Hostname string `gae:"hostname"` 118 // Image is the source image for the boot disk of the GCE instance. 119 Image string `gae:"image"` 120 // Index is this VM's number with respect to its config. 121 Index int32 `gae:"index"` 122 // Lifetime is the number of seconds the GCE instance should live for. 123 Lifetime int64 `gae:"lifetime"` 124 // NetworkInterfaces is a slice of network interfaces attached to this created 125 // GCE instance. Empty if the instance is not yet created. 126 NetworkInterfaces []NetworkInterface `gae:"network_interfaces"` 127 // Prefix is the prefix to use when naming the GCE instance. 128 Prefix string `gae:"prefix"` 129 // Revision is the config revision this VM was created from. 130 Revision string `gae:"revision"` 131 // Swarming is hostname of the Swarming server the GCE instance connects to. 132 Swarming string `gae:"swarming"` 133 // Timeout is the number of seconds the GCE instance has to connect to Swarming. 134 Timeout int64 `gae:"timeout"` 135 // URL is the URL of the created GCE instance. 136 URL string `gae:"url"` 137 } 138 139 // IndexAttributes sets indexable fields of vm.Attributes in AttributesIndexed. 140 func (vm *VM) IndexAttributes() { 141 vm.AttributesIndexed = make([]string, len(vm.Attributes.Disk)) 142 for i, d := range vm.Attributes.Disk { 143 vm.AttributesIndexed[i] = fmt.Sprintf("disk.image:%s", d.GetImageBase()) 144 } 145 } 146 147 // getDisks returns a []*compute.AttachedDisk representation of this VM's disks. 148 func (vm *VM) getDisks() []*compute.AttachedDisk { 149 if len(vm.Attributes.GetDisk()) == 0 { 150 return nil 151 } 152 disks := make([]*compute.AttachedDisk, len(vm.Attributes.Disk)) 153 for i, disk := range vm.Attributes.Disk { 154 disks[i] = &compute.AttachedDisk{ 155 // AutoDelete deletes the disk when the instance is deleted. 156 AutoDelete: true, 157 InitializeParams: &compute.AttachedDiskInitializeParams{ 158 DiskSizeGb: disk.Size, 159 DiskType: disk.Type, 160 SourceImage: disk.Image, 161 }, 162 Interface: disk.GetInterface().String(), 163 } 164 if disk.IsScratchDisk() { 165 disks[i].Type = "SCRATCH" 166 } 167 } 168 // GCE requires the first disk to be the boot disk. 169 disks[0].Boot = true 170 return disks 171 } 172 173 // getMetadata returns a *compute.Metadata representation of this VM's metadata. 174 // vm.DUT is added to the returned metadata if needed. 175 // This ensure that getMetadata always returns the current vm.DUT value. 176 func (vm *VM) getMetadata() *compute.Metadata { 177 if len(vm.Attributes.GetMetadata()) == 0 && vm.DUT == "" { 178 return nil 179 } 180 meta := &compute.Metadata{ 181 Items: make([]*compute.MetadataItems, len(vm.Attributes.Metadata)), 182 } 183 for i, data := range vm.Attributes.Metadata { 184 // Implicitly rejects FromFile, which is only supported in configs. 185 spl := strings.SplitN(data.GetFromText(), ":", 2) 186 // Per strings.SplitN semantics, len(spl) > 0 when splitting on a non-empty separator. 187 // Therefore we can be sure the spl[0] exists (even if it's an empty string). 188 key := spl[0] 189 var val *string 190 if len(spl) > 1 { 191 val = &spl[1] 192 } 193 meta.Items[i] = &compute.MetadataItems{ 194 Key: key, 195 Value: val, 196 } 197 } 198 if vm.DUT != "" { 199 m := &compute.MetadataItems{ 200 Key: "dut", 201 Value: &vm.DUT, 202 } 203 meta.Items = append(meta.Items, m) 204 } 205 return meta 206 } 207 208 // getNetworkInterfaces returns a []*compute.NetworkInterface representation of this VM's network interfaces. 209 func (vm *VM) getNetworkInterfaces() []*compute.NetworkInterface { 210 if len(vm.Attributes.GetNetworkInterface()) == 0 { 211 return nil 212 } 213 nics := make([]*compute.NetworkInterface, len(vm.Attributes.NetworkInterface)) 214 for i, nic := range vm.Attributes.NetworkInterface { 215 nics[i] = &compute.NetworkInterface{ 216 Network: nic.Network, 217 Subnetwork: nic.Subnetwork, 218 } 219 if len(nic.GetAccessConfig()) > 0 { 220 nics[i].AccessConfigs = make([]*compute.AccessConfig, len(nic.AccessConfig)) 221 for j, cfg := range nic.AccessConfig { 222 nics[i].AccessConfigs[j] = &compute.AccessConfig{ 223 Type: cfg.Type.String(), 224 } 225 } 226 } 227 } 228 return nics 229 } 230 231 // getServiceAccounts returns a []*compute.ServiceAccount representation of this VM's service accounts. 232 func (vm *VM) getServiceAccounts() []*compute.ServiceAccount { 233 if len(vm.Attributes.GetServiceAccount()) == 0 { 234 return nil 235 } 236 accts := make([]*compute.ServiceAccount, len(vm.Attributes.ServiceAccount)) 237 for i, sa := range vm.Attributes.ServiceAccount { 238 accts[i] = &compute.ServiceAccount{ 239 Email: sa.Email, 240 } 241 if len(sa.GetScope()) > 0 { 242 accts[i].Scopes = make([]string, len(sa.Scope)) 243 for j, s := range sa.Scope { 244 accts[i].Scopes[j] = s 245 } 246 } 247 } 248 return accts 249 } 250 251 // getTags returns a *compute.Tags representation of this VM's tags. 252 func (vm *VM) getTags() *compute.Tags { 253 if len(vm.Attributes.GetTag()) == 0 { 254 return nil 255 } 256 tags := &compute.Tags{ 257 Items: make([]string, len(vm.Attributes.Tag)), 258 } 259 for i, tag := range vm.Attributes.Tag { 260 tags.Items[i] = tag 261 } 262 return tags 263 } 264 265 func (vm *VM) getLabels() map[string]string { 266 return vm.Attributes.GetLabel() 267 } 268 269 func (vm *VM) getForceSendFields() []string { 270 return vm.Attributes.GetForceSendFields() 271 } 272 273 func (vm *VM) getNullFields() []string { 274 return vm.Attributes.GetNullFields() 275 } 276 277 func (vm *VM) getGCPChannel() config.GCPChannel { 278 return vm.Attributes.GetGcpChannel() 279 } 280 281 // getScheduling returns a *compute.Scheduling representation of this VM's 282 // scheduling options. 283 func (vm *VM) getScheduling() *compute.Scheduling { 284 scheduling := &compute.Scheduling{} 285 opts := vm.Attributes.GetScheduling() 286 affinities := make([]*compute.SchedulingNodeAffinity, len(opts.GetNodeAffinity())) 287 for i, na := range opts.GetNodeAffinity() { 288 affinities[i] = &compute.SchedulingNodeAffinity{ 289 Key: na.Key, 290 Operator: na.Operator.String(), 291 } 292 if len(na.Values) > 0 { 293 affinities[i].Values = make([]string, len(na.Values)) 294 for j, v := range na.Values { 295 affinities[i].Values[j] = v 296 } 297 } 298 } 299 scheduling.NodeAffinities = affinities 300 if vm.Attributes.GetEnableConfidentialCompute() || vm.Attributes.GetTerminateOnMaintenance() { 301 scheduling.OnHostMaintenance = "TERMINATE" 302 } 303 if len(scheduling.NodeAffinities) > 0 || scheduling.OnHostMaintenance == "TERMINATE" { 304 return scheduling 305 } 306 return nil 307 } 308 309 // getShieldedInstanceConfig returns a *compute.ShieldedInstanceConfig 310 // representation of this VM's shielded instance options. 311 func (vm *VM) getShieldedInstanceConfig() *compute.ShieldedInstanceConfig { 312 if !vm.Attributes.DisableIntegrityMonitoring && !vm.Attributes.EnableSecureBoot && !vm.Attributes.DisableVtpm { 313 return nil 314 } 315 return &compute.ShieldedInstanceConfig{ 316 EnableIntegrityMonitoring: !vm.Attributes.DisableIntegrityMonitoring, 317 EnableSecureBoot: vm.Attributes.EnableSecureBoot, 318 EnableVtpm: !vm.Attributes.DisableVtpm, 319 } 320 } 321 322 // getConfidentialInstanceConfig returns a *compute.ConfidentialInstanceConfig 323 // representation of this VM's confidential instance options. 324 func (vm *VM) getConfidentialInstanceConfig() *compute.ConfidentialInstanceConfig { 325 if !vm.Attributes.EnableConfidentialCompute { 326 return nil 327 } 328 return &compute.ConfidentialInstanceConfig{ 329 EnableConfidentialCompute: true, 330 } 331 } 332 333 // A ComputeInstance is a struct containing either a Stable or an Alpha compute instance. 334 // Certain features require the use of the alpha GCP APIs. 335 // 336 // In a valid ComputeInstance instance, exactly one of the fields will be non-nil. 337 type ComputeInstance struct { 338 Stable *compute.Instance 339 Alpha *computealpha.Instance 340 } 341 342 // GetInstance returns a ComputeInstance representation of this VM. 343 func (vm *VM) GetInstance() ComputeInstance { 344 stableInstance := &compute.Instance{ 345 Name: vm.Hostname, 346 ConfidentialInstanceConfig: vm.getConfidentialInstanceConfig(), 347 Disks: vm.getDisks(), 348 MachineType: vm.Attributes.GetMachineType(), 349 Metadata: vm.getMetadata(), 350 MinCpuPlatform: vm.Attributes.GetMinCpuPlatform(), 351 NetworkInterfaces: vm.getNetworkInterfaces(), 352 ServiceAccounts: vm.getServiceAccounts(), 353 Scheduling: vm.getScheduling(), 354 ShieldedInstanceConfig: vm.getShieldedInstanceConfig(), 355 Tags: vm.getTags(), 356 Labels: vm.getLabels(), 357 ForceSendFields: vm.getForceSendFields(), 358 NullFields: vm.getNullFields(), 359 } 360 out := ComputeInstance{} 361 switch vm.getGCPChannel() { 362 case config.GCPChannel_GCP_CHANNEL_ALPHA: 363 alphaInstance := toalpha.Instance(stableInstance) 364 // TODO(gregorynisbet): Add helper function to filter out default values for performance monitoring units. 365 if pmu := vm.Attributes.GetPerformanceMonitoringUnit(); pmu.Number() != 0 { 366 if alphaInstance.AdvancedMachineFeatures == nil { 367 alphaInstance.AdvancedMachineFeatures = &computealpha.AdvancedMachineFeatures{} 368 } 369 alphaInstance.AdvancedMachineFeatures.PerformanceMonitoringUnit = pmu.String() 370 } 371 out.Alpha = alphaInstance 372 default: 373 out.Stable = stableInstance 374 } 375 return out 376 }