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  }