diff --git a/api/v1alpha3/conversion.go b/api/v1alpha3/conversion.go index e40506bc34..8e101afb00 100644 --- a/api/v1alpha3/conversion.go +++ b/api/v1alpha3/conversion.go @@ -18,9 +18,8 @@ package v1alpha3 import ( conversion "k8s.io/apimachinery/pkg/conversion" - ctrlconversion "sigs.k8s.io/controller-runtime/pkg/conversion" - "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha4" + ctrlconversion "sigs.k8s.io/controller-runtime/pkg/conversion" ) var _ ctrlconversion.Convertible = &OpenStackCluster{} @@ -118,3 +117,15 @@ func Convert_v1alpha3_OpenStackClusterSpec_To_v1alpha4_OpenStackClusterSpec(in * func Convert_v1alpha3_OpenStackMachineSpec_To_v1alpha4_OpenStackMachineSpec(in *OpenStackMachineSpec, out *v1alpha4.OpenStackMachineSpec, s conversion.Scope) error { return autoConvert_v1alpha3_OpenStackMachineSpec_To_v1alpha4_OpenStackMachineSpec(in, out, s) } + +// Convert_v1alpha3_OpenStackClusterSpec_To_v1alpha4_OpenStackClusterSpec has to be added by us for the new portOpts +// parameter in v1alpha4. There is no intention to support this parameter in v1alpha3, so the field is just dropped. +func Convert_v1alpha4_Network_To_v1alpha3_Network(in *v1alpha4.Network, out *Network, s conversion.Scope) error { + return autoConvert_v1alpha4_Network_To_v1alpha3_Network(in, out, s) +} + +// Convert_v1alpha3_OpenStackClusterSpec_To_v1alpha4_OpenStackClusterSpec has to be added by us for the new ports +// parameter in v1alpha4. There is no intention to support this parameter in v1alpha3, so the field is just dropped. +func Convert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec(in *v1alpha4.OpenStackMachineSpec, out *OpenStackMachineSpec, s conversion.Scope) error { + return autoConvert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec(in, out, s) +} diff --git a/api/v1alpha3/zz_generated.conversion.go b/api/v1alpha3/zz_generated.conversion.go index 1a3b70eae8..f5af0a1a5a 100644 --- a/api/v1alpha3/zz_generated.conversion.go +++ b/api/v1alpha3/zz_generated.conversion.go @@ -94,11 +94,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1alpha4.Network)(nil), (*Network)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha4_Network_To_v1alpha3_Network(a.(*v1alpha4.Network), b.(*Network), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*NetworkParam)(nil), (*v1alpha4.NetworkParam)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_NetworkParam_To_v1alpha4_NetworkParam(a.(*NetworkParam), b.(*v1alpha4.NetworkParam), scope) }); err != nil { @@ -164,11 +159,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1alpha4.OpenStackMachineSpec)(nil), (*OpenStackMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec(a.(*v1alpha4.OpenStackMachineSpec), b.(*OpenStackMachineSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*OpenStackMachineStatus)(nil), (*v1alpha4.OpenStackMachineStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_OpenStackMachineStatus_To_v1alpha4_OpenStackMachineStatus(a.(*OpenStackMachineStatus), b.(*v1alpha4.OpenStackMachineStatus), scope) }); err != nil { @@ -329,6 +319,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*v1alpha4.Network)(nil), (*Network)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha4_Network_To_v1alpha3_Network(a.(*v1alpha4.Network), b.(*Network), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1alpha4.OpenStackMachineSpec)(nil), (*OpenStackMachineSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec(a.(*v1alpha4.OpenStackMachineSpec), b.(*OpenStackMachineSpec), scope) + }); err != nil { + return err + } return nil } @@ -442,7 +442,14 @@ func autoConvert_v1alpha3_Instance_To_v1alpha4_Instance(in *Instance, out *v1alp out.Trunk = in.Trunk out.FailureDomain = in.FailureDomain out.SecurityGroups = (*[]string)(unsafe.Pointer(in.SecurityGroups)) - out.Networks = (*[]v1alpha4.Network)(unsafe.Pointer(in.Networks)) + if in.Networks != nil { + in, out := &in.Networks, &out.Networks + *out = new([]v1alpha4.Network) + // FIXME: Provide conversion function to convert []Network to []v1alpha4.Network + compileErrorOnMissingConversion() + } else { + out.Networks = nil + } out.Subnet = in.Subnet out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) out.Image = in.Image @@ -470,7 +477,14 @@ func autoConvert_v1alpha4_Instance_To_v1alpha3_Instance(in *v1alpha4.Instance, o out.Trunk = in.Trunk out.FailureDomain = in.FailureDomain out.SecurityGroups = (*[]string)(unsafe.Pointer(in.SecurityGroups)) - out.Networks = (*[]Network)(unsafe.Pointer(in.Networks)) + if in.Networks != nil { + in, out := &in.Networks, &out.Networks + *out = new([]Network) + // FIXME: Provide conversion function to convert []v1alpha4.Network to []Network + compileErrorOnMissingConversion() + } else { + out.Networks = nil + } out.Subnet = in.Subnet out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) out.Image = in.Image @@ -538,16 +552,12 @@ func autoConvert_v1alpha4_Network_To_v1alpha3_Network(in *v1alpha4.Network, out out.ID = in.ID out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) out.Subnet = (*Subnet)(unsafe.Pointer(in.Subnet)) + // WARNING: in.PortOpts requires manual conversion: does not exist in peer-type out.Router = (*Router)(unsafe.Pointer(in.Router)) out.APIServerLoadBalancer = (*LoadBalancer)(unsafe.Pointer(in.APIServerLoadBalancer)) return nil } -// Convert_v1alpha4_Network_To_v1alpha3_Network is an autogenerated conversion function. -func Convert_v1alpha4_Network_To_v1alpha3_Network(in *v1alpha4.Network, out *Network, s conversion.Scope) error { - return autoConvert_v1alpha4_Network_To_v1alpha3_Network(in, out, s) -} - func autoConvert_v1alpha3_NetworkParam_To_v1alpha4_NetworkParam(in *NetworkParam, out *v1alpha4.NetworkParam, s conversion.Scope) error { out.UUID = in.UUID out.FixedIP = in.FixedIP @@ -732,13 +742,37 @@ func Convert_v1alpha4_OpenStackClusterSpec_To_v1alpha3_OpenStackClusterSpec(in * func autoConvert_v1alpha3_OpenStackClusterStatus_To_v1alpha4_OpenStackClusterStatus(in *OpenStackClusterStatus, out *v1alpha4.OpenStackClusterStatus, s conversion.Scope) error { out.Ready = in.Ready - out.Network = (*v1alpha4.Network)(unsafe.Pointer(in.Network)) - out.ExternalNetwork = (*v1alpha4.Network)(unsafe.Pointer(in.ExternalNetwork)) + if in.Network != nil { + in, out := &in.Network, &out.Network + *out = new(v1alpha4.Network) + if err := Convert_v1alpha3_Network_To_v1alpha4_Network(*in, *out, s); err != nil { + return err + } + } else { + out.Network = nil + } + if in.ExternalNetwork != nil { + in, out := &in.ExternalNetwork, &out.ExternalNetwork + *out = new(v1alpha4.Network) + if err := Convert_v1alpha3_Network_To_v1alpha4_Network(*in, *out, s); err != nil { + return err + } + } else { + out.ExternalNetwork = nil + } out.FailureDomains = *(*apiv1alpha4.FailureDomains)(unsafe.Pointer(&in.FailureDomains)) out.ControlPlaneSecurityGroup = (*v1alpha4.SecurityGroup)(unsafe.Pointer(in.ControlPlaneSecurityGroup)) out.WorkerSecurityGroup = (*v1alpha4.SecurityGroup)(unsafe.Pointer(in.WorkerSecurityGroup)) out.BastionSecurityGroup = (*v1alpha4.SecurityGroup)(unsafe.Pointer(in.BastionSecurityGroup)) - out.Bastion = (*v1alpha4.Instance)(unsafe.Pointer(in.Bastion)) + if in.Bastion != nil { + in, out := &in.Bastion, &out.Bastion + *out = new(v1alpha4.Instance) + if err := Convert_v1alpha3_Instance_To_v1alpha4_Instance(*in, *out, s); err != nil { + return err + } + } else { + out.Bastion = nil + } return nil } @@ -749,13 +783,37 @@ func Convert_v1alpha3_OpenStackClusterStatus_To_v1alpha4_OpenStackClusterStatus( func autoConvert_v1alpha4_OpenStackClusterStatus_To_v1alpha3_OpenStackClusterStatus(in *v1alpha4.OpenStackClusterStatus, out *OpenStackClusterStatus, s conversion.Scope) error { out.Ready = in.Ready - out.Network = (*Network)(unsafe.Pointer(in.Network)) - out.ExternalNetwork = (*Network)(unsafe.Pointer(in.ExternalNetwork)) + if in.Network != nil { + in, out := &in.Network, &out.Network + *out = new(Network) + if err := Convert_v1alpha4_Network_To_v1alpha3_Network(*in, *out, s); err != nil { + return err + } + } else { + out.Network = nil + } + if in.ExternalNetwork != nil { + in, out := &in.ExternalNetwork, &out.ExternalNetwork + *out = new(Network) + if err := Convert_v1alpha4_Network_To_v1alpha3_Network(*in, *out, s); err != nil { + return err + } + } else { + out.ExternalNetwork = nil + } out.FailureDomains = *(*apiv1alpha3.FailureDomains)(unsafe.Pointer(&in.FailureDomains)) out.ControlPlaneSecurityGroup = (*SecurityGroup)(unsafe.Pointer(in.ControlPlaneSecurityGroup)) out.WorkerSecurityGroup = (*SecurityGroup)(unsafe.Pointer(in.WorkerSecurityGroup)) out.BastionSecurityGroup = (*SecurityGroup)(unsafe.Pointer(in.BastionSecurityGroup)) - out.Bastion = (*Instance)(unsafe.Pointer(in.Bastion)) + if in.Bastion != nil { + in, out := &in.Bastion, &out.Bastion + *out = new(Instance) + if err := Convert_v1alpha4_Instance_To_v1alpha3_Instance(*in, *out, s); err != nil { + return err + } + } else { + out.Bastion = nil + } return nil } @@ -869,6 +927,7 @@ func autoConvert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec( out.Image = in.Image out.SSHKeyName = in.SSHKeyName out.Networks = *(*[]NetworkParam)(unsafe.Pointer(&in.Networks)) + // WARNING: in.Ports requires manual conversion: does not exist in peer-type out.Subnet = in.Subnet out.FloatingIP = in.FloatingIP out.SecurityGroups = *(*[]SecurityGroupParam)(unsafe.Pointer(&in.SecurityGroups)) @@ -881,11 +940,6 @@ func autoConvert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec( return nil } -// Convert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec is an autogenerated conversion function. -func Convert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec(in *v1alpha4.OpenStackMachineSpec, out *OpenStackMachineSpec, s conversion.Scope) error { - return autoConvert_v1alpha4_OpenStackMachineSpec_To_v1alpha3_OpenStackMachineSpec(in, out, s) -} - func autoConvert_v1alpha3_OpenStackMachineStatus_To_v1alpha4_OpenStackMachineStatus(in *OpenStackMachineStatus, out *v1alpha4.OpenStackMachineStatus, s conversion.Scope) error { out.Ready = in.Ready out.Addresses = *(*[]v1.NodeAddress)(unsafe.Pointer(&in.Addresses)) diff --git a/api/v1alpha4/openstackmachine_types.go b/api/v1alpha4/openstackmachine_types.go index 8708cb1c02..8e2e03a4ee 100644 --- a/api/v1alpha4/openstackmachine_types.go +++ b/api/v1alpha4/openstackmachine_types.go @@ -55,9 +55,13 @@ type OpenStackMachineSpec struct { SSHKeyName string `json:"sshKeyName,omitempty"` // A networks object. Required parameter when there are multiple networks defined for the tenant. - // When you do not specify the networks parameter, the server attaches to the only network created for the current tenant. + // When you do not specify both networks and ports parameters, the server attaches to the only network created for the current tenant. Networks []NetworkParam `json:"networks,omitempty"` + // Ports to be attached to the server instance. They are created if a port with the given name does not already exist. + // When you do not specify both networks and ports parameters, the server attaches to the only network created for the current tenant. + Ports []PortOpts `json:"ports,omitempty"` + // UUID, IP address of a port from this subnet will be marked as AccessIPv4 on the created compute instance Subnet string `json:"subnet,omitempty"` diff --git a/api/v1alpha4/types.go b/api/v1alpha4/types.go index 50c63860a4..ba06fca571 100644 --- a/api/v1alpha4/types.go +++ b/api/v1alpha4/types.go @@ -116,6 +116,32 @@ type SubnetFilter struct { NotTagsAny string `json:"notTagsAny,omitempty"` } +type PortOpts struct { + // ID of the OpenStack network on which to create the port. If unspecified, create the port on the default cluster network. + NetworkID string `json:"networkId,omitempty"` + // Used to make the name of the port unique. If unspecified, instead the 0-based index of the port in the list is used. + NameSuffix string `json:"nameSuffix,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + MACAddress string `json:"macAddress,omitempty"` + // Specify pairs of subnet and/or IP address. These should be subnets of the network with the given NetworkID. + FixedIPs []FixedIP `json:"fixedIps,omitempty"` + TenantID string `json:"tenantId,omitempty"` + ProjectID string `json:"projectId,omitempty"` + SecurityGroups *[]string `json:"securityGroups,omitempty"` + + // The ID of the host where the port is allocated + HostID string `json:"hostId,omitempty"` + + // The virtual network interface card (vNIC) type that is bound to the neutron port. + VNICType string `json:"vnicType,omitempty"` +} + +type FixedIP struct { + SubnetID string `json:"subnetId"` + IPAddress string `json:"ipAddress,omitempty"` +} + type Instance struct { ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` @@ -145,7 +171,7 @@ type RootVolume struct { Size int `json:"diskSize,omitempty"` } -// Network represents basic information about the associated OpenStach Neutron Network. +// Network represents basic information about an OpenStack Neutron Network associated with an instance's port type Network struct { Name string `json:"name"` ID string `json:"id"` @@ -153,8 +179,9 @@ type Network struct { //+optional Tags []string `json:"tags,omitempty"` - Subnet *Subnet `json:"subnet,omitempty"` - Router *Router `json:"router,omitempty"` + Subnet *Subnet `json:"subnet,omitempty"` + PortOpts *PortOpts `json:"port,omitempty"` + Router *Router `json:"router,omitempty"` // Be careful when using APIServerLoadBalancer, because this field is optional and therefore not // set in all cases diff --git a/api/v1alpha4/zz_generated.deepcopy.go b/api/v1alpha4/zz_generated.deepcopy.go index ef1f2e546a..b1a6399193 100644 --- a/api/v1alpha4/zz_generated.deepcopy.go +++ b/api/v1alpha4/zz_generated.deepcopy.go @@ -84,6 +84,21 @@ func (in *Filter) DeepCopy() *Filter { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FixedIP) DeepCopyInto(out *FixedIP) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FixedIP. +func (in *FixedIP) DeepCopy() *FixedIP { + if in == nil { + return nil + } + out := new(FixedIP) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Instance) DeepCopyInto(out *Instance) { *out = *in @@ -169,6 +184,11 @@ func (in *Network) DeepCopyInto(out *Network) { *out = new(Subnet) (*in).DeepCopyInto(*out) } + if in.PortOpts != nil { + in, out := &in.PortOpts, &out.PortOpts + *out = new(PortOpts) + (*in).DeepCopyInto(*out) + } if in.Router != nil { in, out := &in.Router, &out.Router *out = new(Router) @@ -464,6 +484,13 @@ func (in *OpenStackMachineSpec) DeepCopyInto(out *OpenStackMachineSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]PortOpts, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.SecurityGroups != nil { in, out := &in.SecurityGroups, &out.SecurityGroups *out = make([]SecurityGroupParam, len(*in)) @@ -628,6 +655,40 @@ func (in *OpenStackMachineTemplateSpec) DeepCopy() *OpenStackMachineTemplateSpec return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PortOpts) DeepCopyInto(out *PortOpts) { + *out = *in + if in.AdminStateUp != nil { + in, out := &in.AdminStateUp, &out.AdminStateUp + *out = new(bool) + **out = **in + } + if in.FixedIPs != nil { + in, out := &in.FixedIPs, &out.FixedIPs + *out = make([]FixedIP, len(*in)) + copy(*out, *in) + } + if in.SecurityGroups != nil { + in, out := &in.SecurityGroups, &out.SecurityGroups + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortOpts. +func (in *PortOpts) DeepCopy() *PortOpts { + if in == nil { + return nil + } + out := new(PortOpts) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RootVolume) DeepCopyInto(out *RootVolume) { *out = *in diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml index 34fc8986bb..61bf2dc950 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackclusters.yaml @@ -1136,8 +1136,8 @@ spec: networks: description: A networks object. Required parameter when there are multiple networks defined for the tenant. When you do - not specify the networks parameter, the server attaches - to the only network created for the current tenant. + not specify both networks and ports parameters, the server + attaches to the only network created for the current tenant. items: properties: filter: @@ -1241,6 +1241,61 @@ spec: type: string type: object type: array + ports: + description: Ports to be attached to the server instance. + They are created if a port with the given name does not + already exist. When you do not specify both networks and + ports parameters, the server attaches to the only network + created for the current tenant. + items: + properties: + adminStateUp: + type: boolean + description: + type: string + fixedIps: + description: Specify pairs of subnet and/or IP address. + These should be subnets of the network with the given + NetworkID. + items: + properties: + ipAddress: + type: string + subnetId: + type: string + required: + - subnetId + type: object + type: array + hostId: + description: The ID of the host where the port is allocated + type: string + macAddress: + type: string + nameSuffix: + description: Used to make the name of the port unique. + If unspecified, instead the 0-based index of the port + in the list is used. + type: string + networkId: + description: ID of the OpenStack network on which to + create the port. If unspecified, create the port on + the default cluster network. + type: string + projectId: + type: string + securityGroups: + items: + type: string + type: array + tenantId: + type: string + vnicType: + description: The virtual network interface card (vNIC) + type that is bound to the neutron port. + type: string + type: object + type: array providerID: description: ProviderID is the unique identifier as specified by the cloud provider. @@ -1586,8 +1641,8 @@ spec: type: string networks: items: - description: Network represents basic information about the - associated OpenStach Neutron Network. + description: Network represents basic information about an OpenStack + Neutron Network associated with an instance's port properties: apiServerLoadBalancer: description: Be careful when using APIServerLoadBalancer, @@ -1612,6 +1667,54 @@ spec: type: string name: type: string + port: + properties: + adminStateUp: + type: boolean + description: + type: string + fixedIps: + description: Specify pairs of subnet and/or IP address. + These should be subnets of the network with the given + NetworkID. + items: + properties: + ipAddress: + type: string + subnetId: + type: string + required: + - subnetId + type: object + type: array + hostId: + description: The ID of the host where the port is allocated + type: string + macAddress: + type: string + nameSuffix: + description: Used to make the name of the port unique. + If unspecified, instead the 0-based index of the port + in the list is used. + type: string + networkId: + description: ID of the OpenStack network on which to + create the port. If unspecified, create the port on + the default cluster network. + type: string + projectId: + type: string + securityGroups: + items: + type: string + type: array + tenantId: + type: string + vnicType: + description: The virtual network interface card (vNIC) + type that is bound to the neutron port. + type: string + type: object router: description: Router represents basic information about the associated OpenStack Neutron Router. @@ -1820,6 +1923,53 @@ spec: type: string name: type: string + port: + properties: + adminStateUp: + type: boolean + description: + type: string + fixedIps: + description: Specify pairs of subnet and/or IP address. These + should be subnets of the network with the given NetworkID. + items: + properties: + ipAddress: + type: string + subnetId: + type: string + required: + - subnetId + type: object + type: array + hostId: + description: The ID of the host where the port is allocated + type: string + macAddress: + type: string + nameSuffix: + description: Used to make the name of the port unique. If + unspecified, instead the 0-based index of the port in the + list is used. + type: string + networkId: + description: ID of the OpenStack network on which to create + the port. If unspecified, create the port on the default + cluster network. + type: string + projectId: + type: string + securityGroups: + items: + type: string + type: array + tenantId: + type: string + vnicType: + description: The virtual network interface card (vNIC) type + that is bound to the neutron port. + type: string + type: object router: description: Router represents basic information about the associated OpenStack Neutron Router. @@ -1908,6 +2058,53 @@ spec: type: string name: type: string + port: + properties: + adminStateUp: + type: boolean + description: + type: string + fixedIps: + description: Specify pairs of subnet and/or IP address. These + should be subnets of the network with the given NetworkID. + items: + properties: + ipAddress: + type: string + subnetId: + type: string + required: + - subnetId + type: object + type: array + hostId: + description: The ID of the host where the port is allocated + type: string + macAddress: + type: string + nameSuffix: + description: Used to make the name of the port unique. If + unspecified, instead the 0-based index of the port in the + list is used. + type: string + networkId: + description: ID of the OpenStack network on which to create + the port. If unspecified, create the port on the default + cluster network. + type: string + projectId: + type: string + securityGroups: + items: + type: string + type: array + tenantId: + type: string + vnicType: + description: The virtual network interface card (vNIC) type + that is bound to the neutron port. + type: string + type: object router: description: Router represents basic information about the associated OpenStack Neutron Router. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml index a03d8050ec..8c3313ef21 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachines.yaml @@ -431,8 +431,8 @@ spec: networks: description: A networks object. Required parameter when there are multiple networks defined for the tenant. When you do not specify - the networks parameter, the server attaches to the only network - created for the current tenant. + both networks and ports parameters, the server attaches to the only + network created for the current tenant. items: properties: filter: @@ -536,6 +536,58 @@ spec: type: string type: object type: array + ports: + description: Ports to be attached to the server instance. They are + created if a port with the given name does not already exist. When + you do not specify both networks and ports parameters, the server + attaches to the only network created for the current tenant. + items: + properties: + adminStateUp: + type: boolean + description: + type: string + fixedIps: + description: Specify pairs of subnet and/or IP address. These + should be subnets of the network with the given NetworkID. + items: + properties: + ipAddress: + type: string + subnetId: + type: string + required: + - subnetId + type: object + type: array + hostId: + description: The ID of the host where the port is allocated + type: string + macAddress: + type: string + nameSuffix: + description: Used to make the name of the port unique. If unspecified, + instead the 0-based index of the port in the list is used. + type: string + networkId: + description: ID of the OpenStack network on which to create + the port. If unspecified, create the port on the default cluster + network. + type: string + projectId: + type: string + securityGroups: + items: + type: string + type: array + tenantId: + type: string + vnicType: + description: The virtual network interface card (vNIC) type + that is bound to the neutron port. + type: string + type: object + type: array providerID: description: ProviderID is the unique identifier as specified by the cloud provider. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachinetemplates.yaml index 56d948e75d..eeedafd706 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_openstackmachinetemplates.yaml @@ -375,8 +375,8 @@ spec: networks: description: A networks object. Required parameter when there are multiple networks defined for the tenant. When you do - not specify the networks parameter, the server attaches - to the only network created for the current tenant. + not specify both networks and ports parameters, the server + attaches to the only network created for the current tenant. items: properties: filter: @@ -480,6 +480,61 @@ spec: type: string type: object type: array + ports: + description: Ports to be attached to the server instance. + They are created if a port with the given name does not + already exist. When you do not specify both networks and + ports parameters, the server attaches to the only network + created for the current tenant. + items: + properties: + adminStateUp: + type: boolean + description: + type: string + fixedIps: + description: Specify pairs of subnet and/or IP address. + These should be subnets of the network with the given + NetworkID. + items: + properties: + ipAddress: + type: string + subnetId: + type: string + required: + - subnetId + type: object + type: array + hostId: + description: The ID of the host where the port is allocated + type: string + macAddress: + type: string + nameSuffix: + description: Used to make the name of the port unique. + If unspecified, instead the 0-based index of the port + in the list is used. + type: string + networkId: + description: ID of the OpenStack network on which to + create the port. If unspecified, create the port on + the default cluster network. + type: string + projectId: + type: string + securityGroups: + items: + type: string + type: array + tenantId: + type: string + vnicType: + description: The virtual network interface card (vNIC) + type that is bound to the neutron port. + type: string + type: object + type: array providerID: description: ProviderID is the unique identifier as specified by the cloud provider. diff --git a/pkg/cloud/services/compute/instance.go b/pkg/cloud/services/compute/instance.go index d800e6b794..f12616d60e 100644 --- a/pkg/cloud/services/compute/instance.go +++ b/pkg/cloud/services/compute/instance.go @@ -19,6 +19,7 @@ package compute import ( "encoding/json" "fmt" + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding" "net" "os" "strconv" @@ -131,7 +132,28 @@ func (s *Service) CreateInstance(openStackCluster *infrav1.OpenStackCluster, mac if err != nil { return nil, err } - } else { + } + if len(openStackMachine.Spec.Ports) > 0 { + for _, port := range openStackMachine.Spec.Ports { + if port.NetworkID != "" { + nets = append(nets, infrav1.Network{ + ID: port.NetworkID, + Subnet: &infrav1.Subnet{}, + PortOpts: &port, + }) + } else { + nets = append(nets, infrav1.Network{ + ID: openStackCluster.Status.Network.ID, + Subnet: &infrav1.Subnet{ + ID: openStackCluster.Status.Network.Subnet.ID, + }, + PortOpts: &port, + }) + } + } + } + // no networks or ports found in the spec, so create a port on the cluster network + if len(nets) == 0 { nets = []infrav1.Network{{ ID: openStackCluster.Status.Network.ID, Subnet: &infrav1.Subnet{ @@ -152,12 +174,13 @@ func createInstance(is *Service, eventObject runtime.Object, clusterName string, accessIPv4 := "" portList := []servers.Network{} - for _, network := range *i.Networks { + for idx, network := range *i.Networks { if network.ID == "" { return nil, fmt.Errorf("no network was found or provided. Please check your machine configuration and try again") } - port, err := getOrCreatePort(is, eventObject, clusterName, i.Name, network, i.SecurityGroups) + portName := getPortName(i.Name, network.PortOpts, idx) + port, err := getOrCreatePort(is, eventObject, clusterName, portName, network, i.SecurityGroups) if err != nil { return nil, err } @@ -251,6 +274,15 @@ func createInstance(is *Service, eventObject runtime.Object, clusterName string, return instance, nil } +// Different ports must have unique names. +func getPortName(instanceName string, opts *infrav1.PortOpts, netIndex int) string { + if opts.NameSuffix != "" { + return fmt.Sprintf("%s-%s", instanceName, opts.NameSuffix) + } else { + return fmt.Sprintf("%s-%d", instanceName, netIndex) + } +} + // applyRootVolume sets a root volume if the root volume Size is not 0. func applyRootVolume(opts servers.CreateOptsBuilder, rootVolume *infrav1.RootVolume) servers.CreateOptsBuilder { if rootVolume != nil && rootVolume.Size != 0 { @@ -349,7 +381,8 @@ func getServerNetworks(networkClient *gophercloud.ServiceClient, networkParams [ for _, netID := range ids { if networkParam.Subnets == nil { nets = append(nets, infrav1.Network{ - ID: netID, + ID: netID, + Subnet: &infrav1.Subnet{}, }) continue } @@ -376,7 +409,7 @@ func getServerNetworks(networkClient *gophercloud.ServiceClient, networkParams [ return nets, nil } -func getOrCreatePort(is *Service, eventObject runtime.Object, clusterName string, portName string, net infrav1.Network, securityGroups *[]string) (*ports.Port, error) { +func getOrCreatePort(is *Service, eventObject runtime.Object, clusterName string, portName string, net infrav1.Network, instanceSecurityGroups *[]string) (*ports.Port, error) { allPages, err := ports.List(is.networkClient, ports.ListOpts{ Name: portName, NetworkID: net.ID, @@ -384,30 +417,65 @@ func getOrCreatePort(is *Service, eventObject runtime.Object, clusterName string if err != nil { return nil, fmt.Errorf("searching for existing port for server: %v", err) } - portList, err := ports.ExtractPorts(allPages) + existingPorts, err := ports.ExtractPorts(allPages) if err != nil { return nil, fmt.Errorf("searching for existing port for server: %v", err) } - if len(portList) != 0 { - return &portList[0], nil + if len(existingPorts) == 1 { + return &existingPorts[0], nil } - portCreateOpts := ports.CreateOpts{ + if len(existingPorts) > 1 { + return nil, fmt.Errorf("multiple ports found with name \"%s\"", portName) + } + + // no port found, so create the port + description := net.PortOpts.Description + if description == "" { + description = fmt.Sprintf("Created by cluster-api-provider-openstack cluster %s", clusterName) + } + + // inherit port security groups from the instance if not explicitly specified + securityGroups := net.PortOpts.SecurityGroups + if securityGroups == nil { + securityGroups = instanceSecurityGroups + } + + createOpts := ports.CreateOpts{ Name: portName, NetworkID: net.ID, + Description: description, + AdminStateUp: net.PortOpts.AdminStateUp, + MACAddress: net.PortOpts.MACAddress, + TenantID: net.PortOpts.TenantID, + ProjectID: net.PortOpts.ProjectID, SecurityGroups: securityGroups, - Description: fmt.Sprintf("Created by cluster-api-provider-openstack cluster %s", clusterName), + } + + var fixedIPs []ports.IP + for _, fixedIP := range net.PortOpts.FixedIPs { + fixedIPs = append(fixedIPs, ports.IP{ + SubnetID: fixedIP.SubnetID, + IPAddress: fixedIP.IPAddress}) } if net.Subnet.ID != "" { - portCreateOpts.FixedIPs = []ports.IP{{SubnetID: net.Subnet.ID}} + fixedIPs = append(fixedIPs, ports.IP{SubnetID: net.Subnet.ID}) + } + if fixedIPs != nil { + createOpts.FixedIPs = fixedIPs } - port, err := ports.Create(is.networkClient, portCreateOpts).Extract() + + port, err := ports.Create(is.networkClient, portsbinding.CreateOptsExt{ + CreateOptsBuilder: createOpts, + HostID: net.PortOpts.HostID, + VNICType: net.PortOpts.VNICType, + Profile: nil, + }).Extract() if err != nil { record.Warnf(eventObject, "FailedCreatePort", "Failed to create port %s: %v", portName, err) return nil, err } - record.Eventf(eventObject, "SuccessfulCreatePort", "Created port %s with id %s", port.Name, port.ID) return port, nil }