Skip to content

Commit

Permalink
Zones API is supported. Start of the DNS API.
Browse files Browse the repository at this point in the history
  • Loading branch information
liorokman committed May 27, 2023
1 parent 41e6ffa commit efc4ce2
Show file tree
Hide file tree
Showing 4 changed files with 368 additions and 0 deletions.
84 changes: 84 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,90 @@ func main() {
}
log.Printf("Network configuration on node %s has been reverted\n", node)

//SDN
case "applySDN":
exitStatus, err := c.ApplySDN()
if err != nil {
failError(fmt.Errorf("error: %+v\n api error: %s", err, exitStatus))
}
log.Printf("SDN configuration has been applied\n")

case "getZonesList":
zones, err := c.GetSDNZones(true, "")
if err != nil {
log.Printf("Error listing SDN zones %+v\n", err)
os.Exit(1)
}
zonesList, err := json.Marshal(zones)
failError(err)
fmt.Println(string(zonesList))

case "getZone":
if len(flag.Args()) < 2 {
failError(fmt.Errorf("error: Zone name is needed"))
}
zoneName := flag.Args()[1]
zone, err := c.GetSDNZone(zoneName)
if err != nil {
log.Printf("Error listing SDN zones %+v\n", err)
os.Exit(1)
}
zoneList, err := json.Marshal(zone)
failError(err)
fmt.Println(string(zoneList))

case "createZone":
if len(flag.Args()) < 2 {
failError(fmt.Errorf("error: Zone name is needed"))
}
zoneName := flag.Args()[1]
config, err := proxmox.NewConfigSDNZoneFromJson(GetConfig(*fConfigFile))
failError(err)
failError(config.CreateWithValidate(zoneName, c))
log.Printf("Zone %s has been created\n", zoneName)

case "deleteZone":
if len(flag.Args()) < 2 {
failError(fmt.Errorf("error: zone name required"))
}
zoneName := flag.Args()[1]
err := c.DeleteSDNZone(zoneName)
failError(err)

case "updateZone":
if len(flag.Args()) < 2 {
failError(fmt.Errorf("error: zone name required"))
}
zoneName := flag.Args()[1]
config, err := proxmox.NewConfigSDNZoneFromJson(GetConfig(*fConfigFile))
failError(err)
failError(config.UpdateWithValidate(zoneName, c))
log.Printf("Zone %s has been updated\n", zoneName)

case "getDNSList":
dns, err := c.GetSDNDNSs("")
if err != nil {
log.Printf("Error listing SDN DNS entries %+v\n", err)
os.Exit(1)
}
dnsList, err := json.Marshal(dns)
failError(err)
fmt.Println(string(dnsList))

case "getDNS":
if len(flag.Args()) < 2 {
failError(fmt.Errorf("error: DNS name is needed"))
}
name := flag.Args()[1]
dns, err := c.GetSDNDNS(name)
if err != nil {
log.Printf("Error listing SDN DNS %+v\n", err)
os.Exit(1)
}
dnsList, err := json.Marshal(dns)
failError(err)
fmt.Println(string(dnsList))

default:
fmt.Printf("unknown action, try start|stop vmid\n")
}
Expand Down
90 changes: 90 additions & 0 deletions proxmox/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Client struct {
ApiUrl string
Username string
Password string
ApiToken string
Otp string
TaskTimeout int
}
Expand Down Expand Up @@ -1837,6 +1838,95 @@ func (c *Client) RevertNetwork(node string) (exitStatus string, err error) {
return c.DeleteWithTask(url)
}

// SDN

func (c *Client) ApplySDN() (string, error) {
return c.PutWithTask(nil, "/cluster/sdn")
}

// GetSDNDNSs returns a list of all DNS definitions in the "data" element of the returned
// map.
func (c *Client) GetSDNDNSs(typeFilter string) (list map[string]interface{}, err error) {
url := "/cluster/sdn/dns"
if typeFilter != "" {
url += fmt.Sprintf("&type=%s", typeFilter)
}
err = c.GetJsonRetryable(url, &list, 3)
return
}

// CheckSDNDNSExistance returns true if a DNS entry with the provided ID exists, false otherwise.
func (c *Client) CheckSDNDNSExistance(id string) (existance bool, err error) {
list, err := c.GetSDNDNSs("")
existance = ItemInKeyOfArray(list["data"].([]interface{}), "dns", id)
return
}

// GetSDNDNS returns details about the DNS entry whose name was provided.
// An error is returned if the zone doesn't exist.
// The returned zone can be unmarshalled into a ConfigSDNDNS struct.
func (c *Client) GetSDNDNS(name string) (dns map[string]interface{}, err error) {
url := fmt.Sprintf("/cluster/sdn/dns/%s", name)
err = c.GetJsonRetryable(url, &dns, 3)
return
}

// CreateSDNDNS creates a new SDN DNS in the cluster
func (c *Client) CreateSDNDNS(params map[string]interface{}) error {
return c.Post(params, "/cluster/sdn/dns")
}

// DeleteSDNDNS deletes an existing SDN DNS in the cluster
func (c *Client) DeleteSDNDNS(name string) error {
return c.Delete(fmt.Sprintf("/cluster/sdn/dns/%s", name))
}

// UpdateSDNDNS updates the given DNS with the provided parameters
func (c *Client) UpdateSDNDNS(id string, params map[string]interface{}) error {
return c.Put(params, "/cluster/sdn/dns/"+id)
}

// GetSDNZones returns a list of all the SDN zones defined in the cluster.
func (c *Client) GetSDNZones(pending bool, typeFilter string) (list map[string]interface{}, err error) {
url := fmt.Sprintf("/cluster/sdn/zones?pending=%d", Btoi(pending))
if typeFilter != "" {
url += fmt.Sprintf("&type=%s", typeFilter)
}
err = c.GetJsonRetryable(url, &list, 3)
return
}

// CheckSDNZoneExistance returns true if a zone with the provided ID exists, false otherwise.
func (c *Client) CheckSDNZoneExistance(id string) (existance bool, err error) {
list, err := c.GetSDNZones(true, "")
existance = ItemInKeyOfArray(list["data"].([]interface{}), "zone", id)
return
}

// GetSDNZone returns details about the zone whose name was provided.
// An error is returned if the zone doesn't exist.
// The returned zone can be unmarshalled into a ConfigSDNZone struct.
func (c *Client) GetSDNZone(zoneName string) (zone map[string]interface{}, err error) {
url := fmt.Sprintf("/cluster/sdn/zones/%s", zoneName)
err = c.GetJsonRetryable(url, &zone, 3)
return
}

// CreateSDNZone creates a new SDN zone in the cluster
func (c *Client) CreateSDNZone(params map[string]interface{}) error {
return c.Post(params, "/cluster/sdn/zones")
}

// DeleteSDNZone deletes an existing SDN zone in the cluster
func (c *Client) DeleteSDNZone(zoneName string) error {
return c.Delete(fmt.Sprintf("/cluster/sdn/zones/%s", zoneName))
}

// UpdateSDNZone updates the given zone with the provided parameters
func (c *Client) UpdateSDNZone(id string, params map[string]interface{}) error {
return c.Put(params, "/cluster/sdn/zones/"+id)
}

// Shared
func (c *Client) GetItemConfigMapStringInterface(url, text, message string) (map[string]interface{}, error) {
data, err := c.GetItemConfig(url, text, message)
Expand Down
185 changes: 185 additions & 0 deletions proxmox/config_sdn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package proxmox

import (
"encoding/json"
"fmt"
)

// ConfigSDNDNS describes the SDN DNS configurable element
type ConfigSDNDNS struct {
DNS string `json:"dns"`
Key string `json:"key"`
Type string `json:"type"`
URL string `json:"url"`
TTL int `json:"ttl,omitempty"`
// The SDN Plugin schema contains ReverseV6Mask attribute while the
// PowerDNS plugin schema contains the ReverseMaskV6 attribute
// This is probably a bug that crept into the Proxmox implementation.a
// Checked in libpve-network-perl=0.7.3
ReverseMaskV6 int `json:"reversemaskv6,omitempty"`
ReverseV6Mask int `json:"reversev6mask,omitempty"`
// Digest allows for a form of optimistic locking
Digest string `json:"digest,omitempty"`
}

func NewConfigSDNDNSFromJson(input []byte) (config *ConfigSDNDNS, err error) {
config = &ConfigSDNDNS{}
err = json.Unmarshal([]byte(input), config)
return
}

// ConfigSDNZone describes the Zone configurable element
type ConfigSDNZone struct {
Type string `json:"type"`
Zone string `json:"zone"`
AdvertiseSubnets bool `json:"advertise-subnets,omitempty"`
Bridge string `json:"bridge,omitempty"`
BridgeDisableMacLearning bool `json:"bridge-disable-mac-learning,omitempty"`
Controller string `json:"controller,omitempty"`
Delete string `json:"delete,omitempty"`
DisableARPNDSuppression bool `json:"disable-arp-nd-suppression,omitempty"`
DNS string `json:"dns,omitempty"`
DNSZone string `json:"dnszone,omitempty"`
DPID int `json:"dp-id,omitempty"`
ExitNodes string `json:"exitnodes,omitempty"`
ExitNodesLocalRouting bool `json:"exitnodes-local-routing,omitempty"`
ExitNodesPrimary string `json:"exitnodes-primary,omitempty"`
IPAM string `json:"ipam,omitempty"`
MAC string `json:"mac,omitempty"`
MTU int `json:"mtu,omitempty"`
Nodes string `json:"nodes,omitempty"`
Peers string `json:"peers,omitempty"`
ReverseDNS string `json:"reversedns,omitempty"`
RTImport string `json:"rt-import,omitempty"`
Tag int `json:"tag,omitempty"`
VlanProtocol string `json:"vlan-protocol,omitempty"`
VrfVxlan int `json:"vrf-vxlan,omitempty"`
// Digest allows for a form of optimistic locking
Digest string `json:"digest,omitempty"`
}

// NewConfigNetworkFromJSON takes in a byte array from a json encoded SDN Zone
// configuration and stores it in config.
// It returns the newly created config with the passed in configuration stored
// and an error if one occurs unmarshalling the input data.
func NewConfigSDNZoneFromJson(input []byte) (config *ConfigSDNZone, err error) {
config = &ConfigSDNZone{}
err = json.Unmarshal([]byte(input), config)
return
}

func (config *ConfigSDNZone) CreateWithValidate(id string, client *Client) (err error) {
err = config.Validate(id, true, client)
if err != nil {
return
}
return config.Create(id, client)
}

func (config *ConfigSDNZone) Create(id string, client *Client) (err error) {
config.Zone = id
params := config.mapToApiValues()
return client.CreateSDNZone(params)
}

func (config *ConfigSDNZone) UpdateWithValidate(id string, client *Client) (err error) {
err = config.Validate(id, false, client)
if err != nil {
return
}
return config.Update(id, client)
}

func (config *ConfigSDNZone) Update(id string, client *Client) (err error) {
config.Zone = id
params := config.mapToApiValues()
err = client.UpdateSDNZone(id, params)
if err != nil {
params, _ := json.Marshal(&params)
return fmt.Errorf("error updating SDN Zone: %v, (params: %v)", err, string(params))
}
return
}

func (c *ConfigSDNZone) Validate(id string, create bool, client *Client) (err error) {
exists, err := client.CheckSDNZoneExistance(id)
if err != nil {
return
}
if exists && create {
return ErrorItemExists(id, "zone")
}
if !exists && !create {
return ErrorItemNotExists(id, "zone")
}

err = ValidateStringInArray([]string{"evpn", "qinq", "simple", "vlan", "vxlan"}, c.Type, "type")
if err != nil {
return
}
switch c.Type {
case "simple":
case "vlan":
if create {
if c.Bridge == "" {
return ErrorKeyEmpty("bridge")
}
}
case "qinq":
if create {
if c.Bridge == "" {
return ErrorKeyEmpty("bridge")
}
if c.Tag <= 0 {
return ErrorKeyEmpty("tag")
}
if c.VlanProtocol == "" {
return ErrorKeyEmpty("vlan-protocol")
}
}
case "vxlan":
if create {
if c.Peers == "" {
return ErrorKeyEmpty("peers")
}
}
case "evpn":
if create {
if c.VrfVxlan < 0 {
return ErrorKeyEmpty("vrf-vxlan")
}
if c.Controller == "" {
return ErrorKeyEmpty("controller")
}
}
}
if c.VlanProtocol != "" {
err = ValidateStringInArray([]string{"802.1q", "802.1ad"}, c.VlanProtocol, "vlan-protocol")
if err != nil {
return
}
}
return
}

func (config *ConfigSDNZone) mapToApiValues() (params map[string]interface{}) {

d, _ := json.Marshal(config)
json.Unmarshal(d, &params)

boolsToFix := []string{
"advertise-subnets",
"bridge-disable-mac-learning",
"disable-arp-nd-suppression",
"exitnodes-local-routing",
}
for _, key := range boolsToFix {
if v, has := params[key]; has {
params[key] = Btoi(v.(bool))
}
}
// Remove the zone and type (path parameters) from the map
delete(params, "zone")
delete(params, "type")
return
}
9 changes: 9 additions & 0 deletions proxmox/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ func inArray(arr []string, str string) bool {
return false
}

func Btoi(b bool) int {
switch b {
case true:
return 1
default:
return 0
}
}

func Itob(i int) bool {
return i == 1
}
Expand Down

0 comments on commit efc4ce2

Please sign in to comment.