Merge "Pass dynamic IPAM information to vino-buider"

This commit is contained in:
Zuul 2021-04-14 18:29:02 +00:00 committed by Gerrit Code Review
commit 40e7174f1e
12 changed files with 475 additions and 108 deletions

View File

@ -178,7 +178,7 @@ spec:
type: string type: string
type: object type: object
name: name:
description: Parameter for Node master or worker description: Parameter for Node control-plane or worker
type: string type: string
networkDataTemplate: networkDataTemplate:
description: NetworkDataTemplate must have a template key description: NetworkDataTemplate must have a template key

View File

@ -2,6 +2,8 @@ flavorTemplates:
master: master:
domainTemplate: | domainTemplate: |
{% set nodename = 'master-' + item|string %} {% set nodename = 'master-' + item|string %}
{% if domains[nodename] is defined %}
{% set domain = domains[nodename] %}
<domain type="kvm"> <domain type="kvm">
<name>{{ nodename }}</name> <name>{{ nodename }}</name>
<uuid>{{ nodename | hash('md5') }}</uuid> <uuid>{{ nodename | hash('md5') }}</uuid>
@ -71,12 +73,14 @@ flavorTemplates:
</controller> </controller>
# for each interface defined in vino, e.g. # for each interface defined in vino, e.g.
{% for if_name, if_values in domain.interfaces.items() %}
<interface type='bridge'> <interface type='bridge'>
<mac address='52:54:00:83:e9:f9'/> <mac address='{{ if_values.macAddress }}'/>
<source bridge='management'/> <source bridge='{{ if_name }}'/>
<model type='virtio'/> <model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x0{{ loop.index0 }}' function='0x0'/>
</interface> </interface>
{% endfor %}
<serial type="pty"> <serial type="pty">
<source path="/dev/pts/3"/> <source path="/dev/pts/3"/>
@ -103,6 +107,7 @@ flavorTemplates:
<imagelabel>+42424:+104</imagelabel> <imagelabel>+42424:+104</imagelabel>
</seclabel> </seclabel>
</domain> </domain>
{% endif %}
volumeTemplate: | volumeTemplate: |
{% set nodename = 'master-' + item|string %} {% set nodename = 'master-' + item|string %}
<volume> <volume>
@ -116,6 +121,8 @@ flavorTemplates:
worker: worker:
domainTemplate: | domainTemplate: |
{% set nodename = 'worker-' + item|string %} {% set nodename = 'worker-' + item|string %}
{% if domains[nodename] is defined %}
{% set domain = domains[nodename] %}
<domain type="kvm"> <domain type="kvm">
<name>{{ nodename }}</name> <name>{{ nodename }}</name>
<uuid>{{ nodename | hash('md5') }}</uuid> <uuid>{{ nodename | hash('md5') }}</uuid>
@ -184,13 +191,14 @@ flavorTemplates:
<address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x1"/> <address type="pci" domain="0x0000" bus="0x00" slot="0x01" function="0x1"/>
</controller> </controller>
# for each interface defined in vino, e.g. {% for if_name, if_values in domain.interfaces.items() %}
<interface type='bridge'> <interface type='bridge'>
<mac address='52:54:00:83:e9:f9'/> <mac address='{{ if_values.macAddress }}'/>
<source bridge='management'/> <source bridge='{{ if_name }}'/>
<model type='virtio'/> <model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> <address type='pci' domain='0x0000' bus='0x00' slot='0x0{{ loop.index0 }}' function='0x0'/>
</interface> </interface>
{% endfor %}
<serial type="pty"> <serial type="pty">
<source path="/dev/pts/3"/> <source path="/dev/pts/3"/>
@ -217,6 +225,7 @@ flavorTemplates:
<imagelabel>+42424:+104</imagelabel> <imagelabel>+42424:+104</imagelabel>
</seclabel> </seclabel>
</domain> </domain>
{% endif %}
volumeTemplate: | volumeTemplate: |
{% set nodename = 'worker-' + item|string %} {% set nodename = 'worker-' + item|string %}
<volume> <volume>

View File

@ -13,6 +13,8 @@ rules:
verbs: verbs:
- get - get
- list - list
- patch
- update
- watch - watch
- apiGroups: - apiGroups:
- "" - ""

View File

@ -4,6 +4,7 @@ metadata:
name: vino-test-cr name: vino-test-cr
labels: {} labels: {}
spec: spec:
vmBridge: lo
nodeLabelKeysToCopy: nodeLabelKeysToCopy:
- "airshipit.org/server" - "airshipit.org/server"
- "airshipit.org/rack" - "airshipit.org/rack"
@ -24,7 +25,7 @@ spec:
routes: routes:
- network: 10.0.0.0 - network: 10.0.0.0
netmask: 255.255.255.0 netmask: 255.255.255.0
gateway: 192.168.2.1 # vino will need to populate this from the nodelabel value `airshipit.org/vino.nodebridgegw` gateway: $vinobridge # vino will need to populate this from the nodelabel value `airshipit.org/vino.nodebridgegw`
dns_servers: ["135.188.34.124"] dns_servers: ["135.188.34.124"]
- name: external - name: external
subnet: 169.0.0.0/24 subnet: 169.0.0.0/24
@ -36,7 +37,6 @@ spec:
allocationStart: 169.0.0.10 allocationStart: 169.0.0.10
allocationStop: 169.0.0.254 allocationStop: 169.0.0.254
macPrefix: "0A:00:00:00:00:00" macPrefix: "0A:00:00:00:00:00"
vmBridge: lo
nodes: nodes:
- name: master - name: master
count: 1 count: 1

View File

@ -102,6 +102,124 @@ string
</table> </table>
</div> </div>
</div> </div>
<h3 id="airship.airshipit.org/v1.Builder">Builder
</h3>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>gwIPBridge</code><br>
<em>
string
</em>
</td>
<td>
</td>
</tr>
<tr>
<td>
<code>networks</code><br>
<em>
<a href="#airship.airshipit.org/v1.BuilderNetwork">
map[string]./pkg/api/v1.BuilderNetwork
</a>
</em>
</td>
<td>
</td>
</tr>
<tr>
<td>
<code>domains</code><br>
<em>
<a href="#airship.airshipit.org/v1.BuilderDomain">
map[string]./pkg/api/v1.BuilderDomain
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="airship.airshipit.org/v1.BuilderDomain">BuilderDomain
</h3>
<p>
(<em>Appears on:</em>
<a href="#airship.airshipit.org/v1.Builder">Builder</a>)
</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>interfaces</code><br>
<em>
<a href="#airship.airshipit.org/v1.BuilderNetworkInterface">
map[string]./pkg/api/v1.BuilderNetworkInterface
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="airship.airshipit.org/v1.BuilderNetwork">BuilderNetwork
</h3>
<p>
(<em>Appears on:</em>
<a href="#airship.airshipit.org/v1.Builder">Builder</a>)
</p>
<h3 id="airship.airshipit.org/v1.BuilderNetworkInterface">BuilderNetworkInterface
</h3>
<p>
(<em>Appears on:</em>
<a href="#airship.airshipit.org/v1.BuilderDomain">BuilderDomain</a>)
</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>macAddress</code><br>
<em>
string
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="airship.airshipit.org/v1.CPUConfiguration">CPUConfiguration <h3 id="airship.airshipit.org/v1.CPUConfiguration">CPUConfiguration
</h3> </h3>
<p> <p>
@ -796,7 +914,7 @@ string
</em> </em>
</td> </td>
<td> <td>
<p>Parameter for Node master or worker</p> <p>Parameter for Node control-plane or worker</p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -0,0 +1,35 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
type Builder struct {
GWIPBridge string `json:"gwIPBridge,omitempty"`
Networks map[string]BuilderNetwork `json:"networks,omitempty"`
Domains map[string]BuilderDomain `json:"domains,omitempty"`
}
type BuilderNetworkInterface struct {
MACAddress string `json:"macAddress,omitempty"`
}
type BuilderNetwork struct {
// Placeholder for future development
}
type BuilderDomain struct {
Interfaces map[string]BuilderNetworkInterface `json:"interfaces,omitempty"`
}

View File

@ -33,6 +33,10 @@ const (
VinoFinalizer = "vino.airshipit.org" VinoFinalizer = "vino.airshipit.org"
// EnvVarVMInterfaceName environment variable that is used to find VM interface to use for vms // EnvVarVMInterfaceName environment variable that is used to find VM interface to use for vms
EnvVarVMInterfaceName = "VM_BRIDGE_INTERFACE" EnvVarVMInterfaceName = "VM_BRIDGE_INTERFACE"
// VinoDefaultGatewayBridgeLabel is used to identify ip address of the default gateway for the VM
VinoDefaultGatewayBridgeLabel = "airshipit.org/vino.nodebridgegw"
// VinoNodeNetworkValuesAnnotation vino controller saves ip and mac address information for the node in it
VinoNodeNetworkValuesAnnotation = "airshipit.org/vino.network-values"
) )
// VinoSpec defines the desired state of Vino // VinoSpec defines the desired state of Vino
@ -104,7 +108,7 @@ type VMRoutes struct {
//NodeSet node definitions //NodeSet node definitions
type NodeSet struct { type NodeSet struct {
//Parameter for Node master or worker // Parameter for Node control-plane or worker
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Count int `json:"count,omitempty"` Count int `json:"count,omitempty"`
// BMHLabels labels will be copied directly to BMHs that will be created // BMHLabels labels will be copied directly to BMHs that will be created

View File

@ -55,6 +55,87 @@ func (in *BMCCredentials) DeepCopy() *BMCCredentials {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Builder) DeepCopyInto(out *Builder) {
*out = *in
if in.Networks != nil {
in, out := &in.Networks, &out.Networks
*out = make(map[string]BuilderNetwork, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Domains != nil {
in, out := &in.Domains, &out.Domains
*out = make(map[string]BuilderDomain, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Builder.
func (in *Builder) DeepCopy() *Builder {
if in == nil {
return nil
}
out := new(Builder)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BuilderDomain) DeepCopyInto(out *BuilderDomain) {
*out = *in
if in.Interfaces != nil {
in, out := &in.Interfaces, &out.Interfaces
*out = make(map[string]BuilderNetworkInterface, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuilderDomain.
func (in *BuilderDomain) DeepCopy() *BuilderDomain {
if in == nil {
return nil
}
out := new(BuilderDomain)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BuilderNetwork) DeepCopyInto(out *BuilderNetwork) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuilderNetwork.
func (in *BuilderNetwork) DeepCopy() *BuilderNetwork {
if in == nil {
return nil
}
out := new(BuilderNetwork)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BuilderNetworkInterface) DeepCopyInto(out *BuilderNetworkInterface) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BuilderNetworkInterface.
func (in *BuilderNetworkInterface) DeepCopy() *BuilderNetworkInterface {
if in == nil {
return nil
}
out := new(BuilderNetworkInterface)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CPUConfiguration) DeepCopyInto(out *CPUConfiguration) { func (in *CPUConfiguration) DeepCopyInto(out *CPUConfiguration) {
*out = *in *out = *in

View File

@ -19,6 +19,7 @@ import (
"context" "context"
"fmt" "fmt"
"text/template" "text/template"
"time"
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
"github.com/go-logr/logr" "github.com/go-logr/logr"
@ -29,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
kerror "k8s.io/apimachinery/pkg/util/errors" kerror "k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
vinov1 "vino/pkg/api/v1" vinov1 "vino/pkg/api/v1"
"vino/pkg/ipam" "vino/pkg/ipam"
@ -48,8 +50,8 @@ type networkTemplateValues struct {
} }
type generatedValues struct { type generatedValues struct {
IPAddresses map[string]string // a map of network names to IP addresses IPAddresses map[string]string
MACAddresses map[string]string // a map of network interface (link) names to MACs MACAddresses map[string]string
} }
func (r *VinoReconciler) ensureBMHs(ctx context.Context, vino *vinov1.Vino) error { func (r *VinoReconciler) ensureBMHs(ctx context.Context, vino *vinov1.Vino) error {
@ -130,12 +132,13 @@ func (r *VinoReconciler) createIpamNetworks(ctx context.Context, vino *vinov1.Vi
if err != nil { if err != nil {
return err return err
} }
if network.MACPrefix == "" { macPrefix := network.MACPrefix
logger.Info("No MACPrefix provided; using default MACPrefix %s for network %s", if macPrefix == "" {
DefaultMACPrefix, network.Name) logger.Info("No MACPrefix provided; using default MACPrefix for network",
network.MACPrefix = DefaultMACPrefix "default prefix", DefaultMACPrefix, "network name", network.Name)
macPrefix = DefaultMACPrefix
} }
err = r.Ipam.AddSubnetRange(ctx, network.SubNet, subnetRange, network.MACPrefix) err = r.Ipam.AddSubnetRange(ctx, network.SubNet, subnetRange, macPrefix)
if err != nil { if err != nil {
return err return err
} }
@ -145,6 +148,19 @@ func (r *VinoReconciler) createIpamNetworks(ctx context.Context, vino *vinov1.Vi
func (r *VinoReconciler) createBMHperPod(ctx context.Context, vino *vinov1.Vino, pod corev1.Pod) error { func (r *VinoReconciler) createBMHperPod(ctx context.Context, vino *vinov1.Vino, pod corev1.Pod) error {
logger := logr.FromContext(ctx) logger := logr.FromContext(ctx)
nodeNetworkValues := map[string]generatedValues{}
k8sNode, err := r.getNode(ctx, pod)
if err != nil {
return err
}
ip, err := r.getBridgeIP(ctx, k8sNode)
if err != nil {
return err
}
for _, node := range vino.Spec.Nodes { for _, node := range vino.Spec.Nodes {
logger.Info("Creating BMHs for vino node", "node name", node.Name, "count", node.Count) logger.Info("Creating BMHs for vino node", "node name", node.Name, "count", node.Count)
prefix := r.getBMHNodePrefix(vino, pod) prefix := r.getBMHNodePrefix(vino, pod)
@ -152,58 +168,25 @@ func (r *VinoReconciler) createBMHperPod(ctx context.Context, vino *vinov1.Vino,
roleSuffix := fmt.Sprintf("%s-%d", node.Name, i) roleSuffix := fmt.Sprintf("%s-%d", node.Name, i)
bmhName := fmt.Sprintf("%s-%s", prefix, roleSuffix) bmhName := fmt.Sprintf("%s-%s", prefix, roleSuffix)
creds, err := r.reconcileBMHCredentials(ctx, vino) creds, nodeErr := r.reconcileBMHCredentials(ctx, vino)
if err != nil { if nodeErr != nil {
return err return nodeErr
} }
// Allocate an IP for each of this BMH's network interfaces values, nodeErr := r.networkValues(ctx, bmhName, ip, node, vino)
ipAddresses := map[string]string{} if nodeErr != nil {
macAddresses := map[string]string{} return nodeErr
for _, iface := range node.NetworkInterfaces {
networkName := iface.NetworkName
subnet := ""
subnetRange := vinov1.Range{}
for _, network := range vino.Spec.Networks {
if network.Name == networkName {
subnet = network.SubNet
subnetRange, err = ipam.NewRange(network.AllocationStart,
network.AllocationStop)
if err != nil {
return err
} }
break nodeNetworkValues[roleSuffix] = values.Generated
}
} netData, netDataNs, nodeErr := r.reconcileBMHNetworkData(ctx, node, vino, values)
if subnet == "" { if nodeErr != nil {
return fmt.Errorf("Interface %s doesn't have a matching network defined", networkName) return nodeErr
}
ipAllocatedTo := fmt.Sprintf("%s/%s", bmhName, iface.NetworkName)
ipAddress, macAddress, er := r.Ipam.AllocateIP(ctx, subnet, subnetRange, ipAllocatedTo)
if er != nil {
return er
}
ipAddresses[networkName] = ipAddress
macAddresses[iface.Name] = macAddress
} }
values := networkTemplateValues{ bmcAddr, labels, nodeErr := r.getBMCAddressAndLabels(ctx, k8sNode, vino.Spec.NodeLabelKeysToCopy, roleSuffix)
Node: node, if nodeErr != nil {
BMHName: bmhName, return nodeErr
Networks: vino.Spec.Networks,
Generated: generatedValues{
IPAddresses: ipAddresses,
MACAddresses: macAddresses,
},
}
netData, netDataNs, err := r.reconcileBMHNetworkData(ctx, node, vino, values)
if err != nil {
return err
}
bmcAddr, labels, err := r.getBMCAddressAndLabels(ctx, pod, vino.Spec.NodeLabelKeysToCopy, roleSuffix)
if err != nil {
return err
} }
for label, value := range node.BMHLabels { for label, value := range node.BMHLabels {
@ -232,13 +215,145 @@ func (r *VinoReconciler) createBMHperPod(ctx context.Context, vino *vinov1.Vino,
} }
objKey := client.ObjectKeyFromObject(bmh) objKey := client.ObjectKeyFromObject(bmh)
logger.Info("Creating BMH", "name", objKey) logger.Info("Creating BMH", "name", objKey)
err = applyRuntimeObject(ctx, objKey, bmh, r.Client) nodeErr = applyRuntimeObject(ctx, objKey, bmh, r.Client)
if nodeErr != nil {
return nodeErr
}
}
}
logger.Info("annotating node", "node", k8sNode.Name)
if err = r.annotateNode(ctx, ip, k8sNode, nodeNetworkValues); err != nil {
return err
}
return nil
}
func (r *VinoReconciler) networkValues(
ctx context.Context,
bmhName string,
bridgeIP string,
node vinov1.NodeSet,
vino *vinov1.Vino) (networkTemplateValues, error) {
// Allocate an IP for each of this BMH's network interfaces
ipAddresses := map[string]string{}
macAddresses := map[string]string{}
for _, iface := range node.NetworkInterfaces {
networkName := iface.NetworkName
subnet := ""
var err error
subnetRange := vinov1.Range{}
for netIndex, network := range vino.Spec.Networks {
for routeIndex, route := range network.Routes {
if route.Gateway == "$vinobridge" {
vino.Spec.Networks[netIndex].Routes[routeIndex].Gateway = bridgeIP
}
}
if network.Name == networkName {
subnet = network.SubNet
subnetRange, err = ipam.NewRange(network.AllocationStart,
network.AllocationStop)
if err != nil {
return networkTemplateValues{}, err
}
break
}
}
if subnet == "" {
return networkTemplateValues{}, fmt.Errorf("Interface %s doesn't have a matching network defined", networkName)
}
ipAllocatedTo := fmt.Sprintf("%s/%s", bmhName, iface.NetworkName)
ipAddress, macAddress, err := r.Ipam.AllocateIP(ctx, subnet, subnetRange, ipAllocatedTo)
if err != nil {
return networkTemplateValues{}, err
}
ipAddresses[networkName] = ipAddress
macAddresses[iface.Name] = macAddress
logr.FromContext(ctx).Info("Got MAC and IP for the network and node",
"MAC", macAddress, "IP", ipAddress, "bmh name", bmhName)
}
return networkTemplateValues{
Node: node,
BMHName: bmhName,
Networks: vino.Spec.Networks,
Generated: generatedValues{
IPAddresses: ipAddresses,
MACAddresses: macAddresses,
},
}, nil
}
func (r *VinoReconciler) annotateNode(ctx context.Context,
gwIP string,
k8sNode *corev1.Node,
values map[string]generatedValues) error {
logr.FromContext(ctx).Info("Getting GW bridge IP from node", "node", k8sNode.Name)
builderValues := vinov1.Builder{
Domains: make(map[string]vinov1.BuilderDomain),
GWIPBridge: gwIP,
}
for domainName, domain := range values {
builderDomain := vinov1.BuilderDomain{
Interfaces: make(map[string]vinov1.BuilderNetworkInterface),
}
for ifName, ifMAC := range domain.MACAddresses {
builderDomain.Interfaces[ifName] = vinov1.BuilderNetworkInterface{
MACAddress: ifMAC,
}
}
builderValues.Domains[domainName] = builderDomain
}
b, err := yaml.Marshal(builderValues)
if err != nil { if err != nil {
return err return err
} }
annotations := k8sNode.GetAnnotations()
if k8sNode.GetAnnotations() == nil {
annotations = make(map[string]string)
}
annotations[vinov1.VinoNodeNetworkValuesAnnotation] = string(b)
k8sNode.SetAnnotations(annotations)
return r.Update(ctx, k8sNode)
}
func (r *VinoReconciler) getBridgeIP(ctx context.Context, k8sNode *corev1.Node) (string, error) {
ctxTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
for {
select {
case <-ctxTimeout.Done():
return "", ctx.Err()
default:
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: k8sNode.Name,
},
}
if err := r.Get(ctx, client.ObjectKeyFromObject(node), node); err != nil {
return "", err
}
ip, exist := k8sNode.Labels[vinov1.VinoDefaultGatewayBridgeLabel]
if exist {
return ip, nil
}
time.Sleep(10 * time.Second)
} }
} }
return nil }
func (r *VinoReconciler) getNode(ctx context.Context, pod corev1.Pod) (*corev1.Node, error) {
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Spec.NodeName,
},
}
err := r.Get(ctx, client.ObjectKeyFromObject(node), node)
return node, err
} }
func (r *VinoReconciler) getBMHNodePrefix(vino *vinov1.Vino, pod corev1.Pod) string { func (r *VinoReconciler) getBMHNodePrefix(vino *vinov1.Vino, pod corev1.Pod) string {
@ -248,20 +363,10 @@ func (r *VinoReconciler) getBMHNodePrefix(vino *vinov1.Vino, pod corev1.Pod) str
func (r *VinoReconciler) getBMCAddressAndLabels( func (r *VinoReconciler) getBMCAddressAndLabels(
ctx context.Context, ctx context.Context,
pod corev1.Pod, node *corev1.Node,
labelKeys []string, labelKeys []string,
vmName string) (string, map[string]string, error) { vmName string) (string, map[string]string, error) {
logger := logr.FromContext(ctx).WithValues("k8s node", pod.Spec.NodeName) logger := logr.FromContext(ctx).WithValues("k8s node", node.Name)
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Spec.NodeName,
},
}
err := r.Get(ctx, client.ObjectKeyFromObject(node), node)
if err != nil {
return "", nil, err
}
labels := map[string]string{} labels := map[string]string{}
@ -336,6 +441,8 @@ func (r *VinoReconciler) reconcileBMHNetworkData(
return "", "", err return "", "", err
} }
logger.Info("Genereated MAC Addresses values are", "GENERATED VALUES", values.Generated.MACAddresses)
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
err = tpl.Execute(buf, values) err = tpl.Execute(buf, values)
if err != nil { if err != nil {
@ -343,7 +450,6 @@ func (r *VinoReconciler) reconcileBMHNetworkData(
} }
name := fmt.Sprintf("%s-network-data", values.BMHName) name := fmt.Sprintf("%s-network-data", values.BMHName)
ns := getRuntimeNamespace() ns := getRuntimeNamespace()
netSecret := &corev1.Secret{ netSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@ -112,6 +112,7 @@ var _ = Describe("Test BMH reconciliation", func() {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node01", Name: "node01",
Labels: node1Labels, Labels: node1Labels,
Annotations: make(map[string]string),
}, },
Status: corev1.NodeStatus{ Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{ Addresses: []corev1.NodeAddress{
@ -125,6 +126,7 @@ var _ = Describe("Test BMH reconciliation", func() {
node2 := &corev1.Node{ node2 := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node02", Name: "node02",
Annotations: make(map[string]string),
}, },
Status: corev1.NodeStatus{ Status: corev1.NodeStatus{
Addresses: []corev1.NodeAddress{ Addresses: []corev1.NodeAddress{

View File

@ -61,7 +61,7 @@ type VinoReconciler struct {
// +kubebuilder:rbac:groups=airship.airshipit.org,resources=vinoes/status,verbs=get;update;patch // +kubebuilder:rbac:groups=airship.airshipit.org,resources=vinoes/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=airship.airshipit.org,resources=ippools,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=airship.airshipit.org,resources=ippools,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=pods,verbs=list;watch // +kubebuilder:rbac:groups="",resources=pods,verbs=list;watch
// +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch // +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch;update;patch
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch
func (r *VinoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { func (r *VinoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
@ -109,11 +109,6 @@ func (r *VinoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
return ctrl.Result{Requeue: true}, err return ctrl.Result{Requeue: true}, err
} }
err = r.reconcileBMHs(ctx, vino)
if err != nil {
return ctrl.Result{Requeue: true}, err
}
vinov1.VinoReady(vino) vinov1.VinoReady(vino)
if err := r.patchStatus(ctx, vino); err != nil { if err := r.patchStatus(ctx, vino); err != nil {
err = fmt.Errorf("unable to patch status after reconciliation: %w", err) err = fmt.Errorf("unable to patch status after reconciliation: %w", err)
@ -320,10 +315,24 @@ func (r *VinoReconciler) ensureDaemonSet(ctx context.Context, vino *vinov1.Vino)
// controller should watch for changes in daemonset to reconcile if it breaks, and change status // controller should watch for changes in daemonset to reconcile if it breaks, and change status
// of the vino object // of the vino object
// controlleruti.SetControllerReference(vino, ds, r.scheme) // controlleruti.SetControllerReference(vino, ds, r.scheme)
ctx, cancel := context.WithTimeout(ctx, time.Second*180) scheduledTimeoutCtx, cancel := context.WithTimeout(ctx, time.Second*180)
defer cancel() defer cancel()
return r.waitDaemonSet(ctx, ds) logger := logr.FromContext(ctx)
logger.Info("Waiting for daemonset to become scheduled")
if err = r.waitDaemonSet(scheduledTimeoutCtx, dsScheduled, ds); err != nil {
return err
}
if err = r.reconcileBMHs(ctx, vino); err != nil {
return err
}
waitTimeoutCtx, cancel := context.WithTimeout(ctx, time.Second*180)
defer cancel()
logger.Info("Waiting for daemonset to become ready")
return r.waitDaemonSet(waitTimeoutCtx, dsReady, ds)
} }
func (r *VinoReconciler) decorateDaemonSet(ctx context.Context, ds *appsv1.DaemonSet, vino *vinov1.Vino) { func (r *VinoReconciler) decorateDaemonSet(ctx context.Context, ds *appsv1.DaemonSet, vino *vinov1.Vino) {
@ -414,7 +423,7 @@ func setEnv(ctx context.Context, ds *appsv1.DaemonSet, vino *vinov1.Vino) {
} }
} }
func (r *VinoReconciler) waitDaemonSet(ctx context.Context, ds *appsv1.DaemonSet) error { func (r *VinoReconciler) waitDaemonSet(ctx context.Context, check dsWaitCondition, ds *appsv1.DaemonSet) error {
logger := logr.FromContext(ctx).WithValues( logger := logr.FromContext(ctx).WithValues(
"daemonset", ds.Namespace+"/"+ds.Name) "daemonset", ds.Namespace+"/"+ds.Name)
for { for {
@ -429,12 +438,11 @@ func (r *VinoReconciler) waitDaemonSet(ctx context.Context, ds *appsv1.DaemonSet
Namespace: ds.Namespace, Namespace: ds.Namespace,
}, getDS) }, getDS)
if err != nil { if err != nil {
logger.Info("received error while waiting for ds to become ready, sleeping", logger.Info("received error while waiting for ds to reach desired condition, sleeping",
"error", err.Error()) "error", err.Error())
} else { } else {
logger.Info("checking daemonset status", "status", getDS.Status) logger.Info("checking daemonset status", "status", getDS.Status)
if getDS.Status.DesiredNumberScheduled == getDS.Status.NumberReady && if check(getDS) {
getDS.Status.DesiredNumberScheduled != 0 {
logger.Info("DaemonSet is in ready status") logger.Info("DaemonSet is in ready status")
return nil return nil
} }
@ -527,3 +535,13 @@ func applyRuntimeObject(ctx context.Context, key client.ObjectKey, obj client.Ob
func getRuntimeNamespace() string { func getRuntimeNamespace() string {
return os.Getenv("RUNTIME_NAMESPACE") return os.Getenv("RUNTIME_NAMESPACE")
} }
func dsScheduled(ds *appsv1.DaemonSet) bool {
return ds.Status.DesiredNumberScheduled != 0 && ds.Status.DesiredNumberScheduled == ds.Status.CurrentNumberScheduled
}
func dsReady(ds *appsv1.DaemonSet) bool {
return ds.Status.DesiredNumberScheduled != 0 && ds.Status.DesiredNumberScheduled == ds.Status.NumberReady
}
type dsWaitCondition func(ds *appsv1.DaemonSet) bool

View File

@ -28,25 +28,17 @@
collect_namespaced_objects: collect_namespaced_objects:
- baremetalhosts - baremetalhosts
- configmaps - configmaps
- cronjobs
- daemonsets - daemonsets
- deployments - deployments
- endpoints
- ingresses
- jobs
- networkpolicies
- pods - pods
- podsecuritypolicies
- persistentvolumeclaims
- replicasets - replicasets
- rolebindings - rolebindings
- roles - roles
- secrets - secrets
- serviceaccounts - serviceaccounts
- services
- statefulsets
- vino - vino
- nodes
- ippool
- name: "Get context list" - name: "Get context list"
include_tasks: get-contexts.yaml include_tasks: get-contexts.yaml