Skip to content

Commit

Permalink
Merge pull request #145 from mkumatag/dhcp
Browse files Browse the repository at this point in the history
dhcp-sync: sync dhcp config with powervs network
  • Loading branch information
ltccci authored May 25, 2021
2 parents c4a06cd + 820c753 commit 1b375ad
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 0 deletions.
203 changes: 203 additions & 0 deletions cmd/dhcp-sync/dhcp-sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright 2021 IBM Corp
//
// 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 dhcp

import (
"fmt"
"io/ioutil"
"net"
"strings"
"sync"
"time"

"github.com/fsnotify/fsnotify"
"github.com/spf13/cobra"
"k8s.io/klog/v2"

"github.com/ppc64le-cloud/pvsadm/pkg"
"github.com/ppc64le-cloud/pvsadm/pkg/client"
"github.com/ppc64le-cloud/pvsadm/pkg/utils"

. "github.com/sayotte/iscdhcp"
)

var (
gateway, networkID, file, nameservers string
mutex = &sync.Mutex{}
)

func ipv4MaskString(m []byte) string {
if len(m) != 4 {
panic("ipv4Mask: len must be 4 bytes")
}

return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
}

const dhcpdTemplate = `
default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;
authoritative;
%s
`

func doEvery(d time.Duration, f func()) {
ticker := time.NewTicker(d)
defer ticker.Stop()
for ; true; <-ticker.C {
f()
}
}

type RoutersOption []net.IP

// IndentedString implements the method of the same name in the Statement interface
func (ro RoutersOption) IndentedString(prefix string) string {
s := prefix + "option routers "
for i := 0; i < len(ro)-1; i++ {
s += ro[i].String() + ", "
}
return s + ro[len(ro)-1].String() + ";\n"
}

func syncDHCPD() {
c, err := client.NewClientWithEnv(pkg.Options.APIKey, pkg.Options.Environment, false)
if err != nil {
klog.Fatalf("failed to create a session with IBM cloud: %v", err)
}

pvmclient, err := client.NewPVMClientWithEnv(c, pkg.Options.InstanceID, "", "prod")
if err != nil {
klog.Fatalf("failed to create a PVM client: %v", err)
}

n, err := pvmclient.NetworkClient.Get(networkID)

ipv4Addr, ipv4Net, err := net.ParseCIDR(*n.Cidr)
if err != nil {
klog.Fatalf("failed to ParseCIDR: %v", err)
}
subnetStmt := SubnetStatement{
SubnetNumber: ipv4Addr,
Netmask: net.ParseIP(ipv4MaskString(ipv4Net.Mask)),
}

ports, err := pvmclient.NetworkClient.GetAllPort(networkID)
if err != nil {
klog.Fatalf("failed to get the ports: %v", err)
}

var g = n.Gateway

if gateway != "" {
g = gateway
}

router := RoutersOption{net.ParseIP(g)}
subnetStmt.Statements = append(subnetStmt.Statements, router)

nameserver := DomainNameServersOption{}
var nsList = n.DNSServers
if nameservers != "" {
nsList = strings.Split(nameservers, ",")
}

for _, ns := range nsList {
nameserver = append(nameserver, net.ParseIP(ns))
}
subnetStmt.Statements = append(subnetStmt.Statements, nameserver)

for _, port := range ports.Ports {
hs := HostStatement{
Hostname: "host" + strings.Split(*port.IPAddress, ".")[3],
}
hs.Statements = append(hs.Statements, HardwareStatement{HardwareType: "ethernet", HardwareAddress: *port.MacAddress})
hs.Statements = append(hs.Statements, FixedAddressStatement{net.ParseIP(*port.IPAddress)})
subnetStmt.Statements = append(subnetStmt.Statements, hs)
}

content := fmt.Sprintf(dhcpdTemplate, subnetStmt.IndentedString(""))
mutex.Lock()
ioutil.WriteFile(file, []byte(content), 0644)
mutex.Unlock()
}

var Cmd = &cobra.Command{
Use: "dhcp-sync",
Short: "dhcp-sync command",
Long: `dhcp-sync tool is a tool populating the dhcpd.conf file from the PowerVS network and restart the dhcpd service.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if pkg.Options.InstanceID == "" {
return fmt.Errorf("--instance-id is required")
}
if pkg.Options.APIKey == "" {
return fmt.Errorf("api-key can't be empty, pass the token via --api-key or set IBMCLOUD_API_KEY environment variable")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
go doEvery(2*time.Minute, syncDHCPD)

watcher, err := fsnotify.NewWatcher()
if err != nil {
klog.Fatal(err)
}
defer watcher.Close()

done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
klog.Info("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
klog.Info("modified file:", event.Name)
klog.Info("restarting the dhcpd service")
exitcode, out, err := utils.RunCMD("systemctl", "restart", "dhcpd")
if exitcode != 0 {
klog.Errorf("failed to restart the dhcpd service, exitcode: %d, stdout: %s, err: %s", exitcode, out, err)
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
klog.Error("error:", err)
}
}
}()

err = watcher.Add(file)
if err != nil {
klog.Fatal(err)
}
<-done
return nil
},
}

func init() {
Cmd.Flags().StringVarP(&pkg.Options.InstanceID, "instance-id", "i", "", "Instance ID of the PowerVS instance")
Cmd.Flags().StringVar(&networkID, "network-id", "", "Network ID to be monitored")
Cmd.Flags().StringVar(&file, "file", "/etc/dhcp/dhcpd.conf", "DHCP conf file")
Cmd.Flags().StringVar(&gateway, "gateway", "", "Override the gateway value with")
Cmd.Flags().StringVar(&nameservers, "nameservers", "", "Override the DNS nameservers")
_ = Cmd.MarkFlagRequired("network-id")
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/ppc64le-cloud/pvsadm/cmd/create"
deletecmd "github.com/ppc64le-cloud/pvsadm/cmd/delete"
"github.com/ppc64le-cloud/pvsadm/cmd/dhcp-sync"
"github.com/ppc64le-cloud/pvsadm/cmd/get"
"github.com/ppc64le-cloud/pvsadm/cmd/image"
"github.com/ppc64le-cloud/pvsadm/cmd/purge"
Expand Down Expand Up @@ -67,6 +68,7 @@ func init() {
rootCmd.AddCommand(image.Cmd)
rootCmd.AddCommand(create.Cmd)
rootCmd.AddCommand(deletecmd.Cmd)
rootCmd.AddCommand(dhcp.Cmd)
rootCmd.PersistentFlags().StringVarP(&pkg.Options.APIKey, "api-key", "k", "", "IBMCLOUD API Key(env name: IBMCLOUD_API_KEY)")
rootCmd.PersistentFlags().StringVar(&pkg.Options.Environment, "env", client.DefaultEnv, "IBM Cloud Environments, supported are: ["+strings.Join(client.ListEnvironments(), ", ")+"]")
rootCmd.PersistentFlags().BoolVar(&pkg.Options.Debug, "debug", false, "Enable PowerVS debug option(ATTENTION: dev only option, may print sensitive data from APIs)")
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/IBM/ibm-cos-sdk-go v1.5.0
github.com/IBM/platform-services-go-sdk v0.14.4
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fsnotify/fsnotify v1.4.9
github.com/go-openapi/strfmt v0.19.10
github.com/klauspost/compress v1.11.1 // indirect
github.com/klauspost/pgzip v1.2.5
Expand All @@ -18,6 +19,7 @@ require (
github.com/onsi/ginkgo v1.14.2
github.com/onsi/gomega v1.10.3
github.com/ppc64le-cloud/powervs-utils v0.0.0-20210415051532-4cdd6a79c8fa
github.com/sayotte/iscdhcp v0.0.0-20190926162140-d6be84ba9969
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
k8s.io/apimachinery v0.20.0
Expand Down
Loading

0 comments on commit 1b375ad

Please sign in to comment.