19 changed files with 573 additions and 218 deletions
@ -1,22 +1,32 @@
|
||||
package podadm |
||||
|
||||
//TODO network interface management
|
||||
import ( |
||||
"os" |
||||
|
||||
// The following types will be available.
|
||||
"github.com/olekukonko/tablewriter" |
||||
"github.com/spf13/cobra" |
||||
"github.com/ztrue/tracerr" |
||||
) |
||||
|
||||
// shared
|
||||
// A simple shared ip-stack network thus the pod will be available over the same ip as the GZ
|
||||
var networkCMD = &cobra.Command{ |
||||
Use: "network", |
||||
Short: "network", |
||||
Aliases: []string{"networks", "net"}, |
||||
RunE: func(cmd *cobra.Command, args []string) error { |
||||
tw := tablewriter.NewWriter(os.Stdout) |
||||
nets, err := h.Networks() |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
tw.SetHeader([]string{"Name", "Address Range", "Interface", "DNS Options", "Router"}) |
||||
for _, n := range nets { |
||||
tw.Append([]string{n.Name, n.AllowedAddr, n.Lowerlink, n.DNSOptions.String(), n.DefaultRouter}) |
||||
} |
||||
tw.Render() |
||||
return nil |
||||
}, |
||||
} |
||||
|
||||
// vnic
|
||||
// A simple vnic attached to a link of the global zone.
|
||||
// NAT is possible via the GZ
|
||||
// Can join the same network as the GZ
|
||||
// Will copy the network setup of the GZ NIC
|
||||
// Every pod will have one IP and the apps will need to be available on different ports even if they are different zones
|
||||
// VLAN is available
|
||||
|
||||
// etherstub
|
||||
// A complete switching infrastructure for the pods it is connected on.
|
||||
// every Pod has one IP even though every app is it's own zone
|
||||
// NAT must be provided by a dedicated zone.
|
||||
// VLAN for Host interconnect is available
|
||||
func init() { |
||||
RootCmd.AddCommand(networkCMD) |
||||
} |
||||
|
@ -1,25 +1,81 @@
|
||||
package host |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
opcNet "git.wegmueller.it/opencloud/opencloud/net" |
||||
"git.wegmueller.it/opencloud/opencloud/pod" |
||||
"github.com/docker/docker/pkg/fileutils" |
||||
"github.com/sirupsen/logrus" |
||||
"github.com/ztrue/tracerr" |
||||
) |
||||
|
||||
func (h *Host) AddNetwork(name, physical, allowedAddressRange, defrouter string, networkType int) error { |
||||
panic("implement me") |
||||
func (h *Host) AddNetwork(name, allowedAddressRange, physical, defrouter string, dnsOptions []string, networkType opcNet.NetworkType) (*opcNet.Network, error) { |
||||
return opcNet.CreateNetwork(name, allowedAddressRange, physical, defrouter, dnsOptions, networkType) |
||||
} |
||||
|
||||
func (h *Host) ChangeNetwork(name, physical, allowedAddressRange, defrouter string, networkType int) error { |
||||
panic("implement me") |
||||
func (h *Host) ChangeNetwork(name, allowedAddressRange, physical, defrouter string, dnsOptions []string, networkType opcNet.NetworkType) (*opcNet.Network, error) { |
||||
net, err := opcNet.GetNetwork(name) |
||||
if err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
net.Update(allowedAddressRange, physical, defrouter, dnsOptions, networkType) |
||||
if err = net.Save(); err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
return net, nil |
||||
} |
||||
|
||||
func (h *Host) GetNetwork(name string) *opcNet.Network { |
||||
panic("implement me") |
||||
func (h *Host) GetNetwork(name string) (*opcNet.Network, error) { |
||||
return opcNet.GetNetwork(name) |
||||
} |
||||
|
||||
func (h *Host) Networks() []*opcNet.Network { |
||||
panic("implement me") |
||||
func (h *Host) Networks() ([]*opcNet.Network, error) { |
||||
return opcNet.ListNetworks() |
||||
} |
||||
|
||||
func (h *Host) RemoveNetwork(name string) error { |
||||
panic("implement me") |
||||
net, err := opcNet.GetNetwork(name) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
|
||||
return net.Remove() |
||||
} |
||||
|
||||
func (h *Host) setupDNSInZone(c *pod.Container, n *opcNet.Network) error { |
||||
if c.Zone.Brand == pod.BrandBHyve || c.Zone.Brand == pod.BrandKVM { |
||||
return nil |
||||
} |
||||
logrus.Debugf("setting up resolv.conf inside %s(%s)", c.UUID, c.Name) |
||||
//Render /etc/resolv.conf for zone if brand is not bhyve or kvm
|
||||
resolvConfPath := filepath.Join(c.Zone.Zonepath, "root/etc/resolv.conf") |
||||
buff := bytes.NewBuffer(nil) |
||||
for key, value := range n.DNSOptions { |
||||
if strings.Contains(value, ";") { |
||||
mult := strings.Split(value, ";") |
||||
for _, val := range mult { |
||||
buff.WriteString(fmt.Sprintf("%s=%s\n", key, val)) |
||||
} |
||||
} else { |
||||
buff.WriteString(fmt.Sprintf("%s=%s\n", key, value)) |
||||
} |
||||
} |
||||
if err := ioutil.WriteFile(resolvConfPath, buff.Bytes(), 0644); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
|
||||
logrus.Debugf("setting up nsswitch.conf inside %s(%s)", c.UUID, c.Name) |
||||
//copy /etc/nsswitch.dns /etc/nsswitch.conf inside the zone
|
||||
nsswitchDnsPath := filepath.Join(c.Zone.Zonepath, "root/etc/nsswitch.dns") |
||||
nsswitchConfPath := filepath.Join(c.Zone.Zonepath, "root/etc/nsswitch.conf") |
||||
if _, err := fileutils.CopyFile(nsswitchDnsPath, nsswitchConfPath); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
@ -1,46 +1,21 @@
|
||||
package net |
||||
|
||||
import ( |
||||
"git.wegmueller.it/illumos/go-zone/config" |
||||
"github.com/teris-io/shortid" |
||||
"github.com/ztrue/tracerr" |
||||
) |
||||
|
||||
const NicABC = "0123456789abcdefghijklmnopqrstuvwxyz" |
||||
|
||||
func init() { |
||||
sid, err := shortid.New(1, NicABC, 86755) |
||||
sid, err := shortid.New(1, shortid.DefaultABC, 86755) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
shortid.SetDefault(sid) |
||||
} |
||||
|
||||
func setupDirectNetworking(n *Network, z *config.Zone) error { |
||||
//Setup VNIC
|
||||
// Nic Name must be shorter than a UUID
|
||||
nicName, err := shortid.Generate() |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
|
||||
if out, err := runDladm("create-vnic", "-l", n.Physical, nicName+"0"); err != nil { |
||||
func setupDirectNetworkingNic(n *Network, nicName string) error { |
||||
if out, err := runDladm("create-vnic", "-l", n.Lowerlink, nicName); err != nil { |
||||
return tracerr.Errorf("dladm create-vnic failed: %s", out) |
||||
} |
||||
|
||||
ipaddr, err := getAddressFromPool(n.CIDR, n.PoolStart, n.PoolEnd, len(n.AddressLease)) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
n.AddressLease = append(n.AddressLease, ipaddr) |
||||
|
||||
//Assign VNIC to zone
|
||||
//Assign address from pool range to zone
|
||||
z.Networks = append(z.Networks, config.Network{ |
||||
Physical: nicName, |
||||
AllowedAddress: ipaddr, |
||||
Defrouter: n.DefaultRouter, |
||||
}) |
||||
//Render /etc/resolv.conf for zone if brand is not bhyve or kvm
|
||||
//copy /etc/nsswitch.dns /etc/nsswitch.conf inside the zone
|
||||
return nil |
||||
} |
||||
|
@ -0,0 +1,217 @@
|
||||
//go:generate stringer -type=NetworkType
|
||||
package net |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"git.wegmueller.it/opencloud/opencloud/config" |
||||
"github.com/opencontainers/runtime-spec/specs-go" |
||||
"github.com/spf13/viper" |
||||
"github.com/teris-io/shortid" |
||||
"github.com/ztrue/tracerr" |
||||
) |
||||
|
||||
type NetworkType int |
||||
|
||||
const ( |
||||
NetworkTypeNat NetworkType = iota |
||||
NetworkTypeVXLAN |
||||
NetworkTypeVLAN |
||||
NetworkTypeDirect |
||||
NetworkTypeUnset = 999 |
||||
) |
||||
|
||||
const ( |
||||
DefaultNetworkName = "default" |
||||
) |
||||
|
||||
type DNSOptions map[string]string |
||||
|
||||
func (d DNSOptions) String() string { |
||||
sw := strings.Builder{} |
||||
for k, v := range d { |
||||
if strings.Contains(v, ";") { |
||||
mult := strings.Split(v, ";") |
||||
for _, val := range mult { |
||||
sw.WriteString(fmt.Sprintf("%s=%s\n", k, val)) |
||||
} |
||||
} else { |
||||
sw.WriteString(fmt.Sprintf("%s=%s\n", k, v)) |
||||
} |
||||
} |
||||
return sw.String()[:sw.Len()-1] |
||||
} |
||||
|
||||
type Network struct { |
||||
Type NetworkType `json:"type"` |
||||
Name string `json:"name"` |
||||
AllowedAddr string `json:"allowed_addr"` |
||||
PoolStart int `json:"pool_start"` |
||||
PoolEnd int `json:"pool_end"` |
||||
AddressLease []string `json:"address_lease"` |
||||
Lowerlink string `json:"physical"` |
||||
DefaultRouter string `json:"defrouter"` |
||||
DNSOptions DNSOptions `json:"dns_options"` |
||||
Linkprotection string `json:"link_protection"` |
||||
} |
||||
|
||||
func NewNetwork(name string) *Network { |
||||
return &Network{Name: name, AddressLease: make([]string, 0), PoolEnd: 254, PoolStart: 128, DNSOptions: make(DNSOptions), Type: NetworkTypeUnset} |
||||
} |
||||
|
||||
func (n *Network) Save() error { |
||||
if n.Lowerlink == "" || n.Type == NetworkTypeUnset { |
||||
return fmt.Errorf("network configuration not capable to be setup") |
||||
} |
||||
f, err := os.OpenFile(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net", n.Name+".json"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
defer f.Close() |
||||
if err := json.NewEncoder(f).Encode(n); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (n *Network) Load() error { |
||||
f, err := os.Open(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net", n.Name+".json")) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
defer f.Close() |
||||
if err := json.NewDecoder(f).Decode(n); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (n *Network) SetupNic() (specs.SolarisAnet, error) { |
||||
nicName, err := createNicName() |
||||
if err != nil { |
||||
return specs.SolarisAnet{}, tracerr.Wrap(err) |
||||
} |
||||
|
||||
ipaddr, err := getAddressFromPool(n.AllowedAddr, n.PoolStart, n.PoolEnd, len(n.AddressLease)) |
||||
if err != nil { |
||||
return specs.SolarisAnet{}, tracerr.Wrap(err) |
||||
} |
||||
n.AddressLease = append(n.AddressLease, ipaddr) |
||||
|
||||
if err = n.Save(); err != nil { |
||||
return specs.SolarisAnet{}, tracerr.Wrap(err) |
||||
} |
||||
switch n.Type { |
||||
case NetworkTypeDirect: |
||||
if err := setupDirectNetworkingNic(n, nicName); err != nil { |
||||
return specs.SolarisAnet{}, tracerr.Wrap(err) |
||||
} |
||||
default: |
||||
return specs.SolarisAnet{}, fmt.Errorf("network type %s is not implemented", n.Type) |
||||
} |
||||
return specs.SolarisAnet{ |
||||
Linkname: nicName, |
||||
Lowerlink: n.Lowerlink, |
||||
Allowedaddr: ipaddr, |
||||
Defrouter: n.DefaultRouter, |
||||
Linkprotection: n.Linkprotection, |
||||
}, nil |
||||
} |
||||
|
||||
func (n *Network) Remove() error { |
||||
//TODO Remove links that are associated with this network
|
||||
return os.Remove(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net", n.Name+".json")) |
||||
} |
||||
|
||||
func (n *Network) Update(addressPool, physical, defRouter string, dnsOptions []string, netType NetworkType) { |
||||
if addressPool != "" { |
||||
n.AllowedAddr = addressPool |
||||
} |
||||
|
||||
if physical != "" { |
||||
n.Lowerlink = physical |
||||
} |
||||
|
||||
if defRouter != "" { |
||||
n.DefaultRouter = defRouter |
||||
} |
||||
|
||||
for _, dnsOpt := range dnsOptions { |
||||
if idx := strings.Index(dnsOpt, "="); idx != -1 { |
||||
if oldVal, ok := n.DNSOptions[dnsOpt[:idx]]; ok { |
||||
n.DNSOptions[dnsOpt[:idx]] = oldVal + ";" + dnsOpt[idx+1:] |
||||
} else { |
||||
n.DNSOptions[dnsOpt[:idx]] = dnsOpt[idx+1:] |
||||
} |
||||
} |
||||
} |
||||
|
||||
n.Type = netType |
||||
} |
||||
|
||||
func createNicName() (string, error) { |
||||
nicName, err := shortid.Generate() |
||||
if err != nil { |
||||
return "", tracerr.Wrap(err) |
||||
} |
||||
for _, r := range "0987654321-_" { |
||||
nicName = strings.Replace(nicName, string(r), "", -1) |
||||
} |
||||
|
||||
nicNumber, err := shortid.Generate() |
||||
if err != nil { |
||||
return "", tracerr.Wrap(err) |
||||
} |
||||
for _, r := range "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_" { |
||||
nicNumber = strings.Replace(nicNumber, string(r), "", -1) |
||||
} |
||||
return nicName + nicNumber + "0", nil |
||||
} |
||||
|
||||
func CreateNetwork(name, addressPool, physical, defRouter string, dnsOptions []string, netType NetworkType) (*Network, error) { |
||||
n := NewNetwork(name) |
||||
n.Update(addressPool, physical, defRouter, dnsOptions, netType) |
||||
if err := n.Save(); err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
return n, nil |
||||
} |
||||
|
||||
func GetNetwork(name string) (*Network, error) { |
||||
n := &Network{Name: name} |
||||
if err := n.Load(); err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
return n, nil |
||||
} |
||||
|
||||
func ListNetworks() ([]*Network, error) { |
||||
matches, err := filepath.Glob(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net") + "/*.json") |
||||
if err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
n := make([]*Network, 0) |
||||
for _, m := range matches { |
||||
netw := Network{} |
||||
err := func() error { |
||||
f, err := os.Open(m) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
defer f.Close() |
||||
if err := json.NewDecoder(f).Decode(&netw); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
return nil |
||||
}() |
||||
if err != nil { |
||||
continue |
||||
} |
||||
n = append(n, &netw) |
||||
} |
||||
return n, nil |
||||
} |
@ -1,129 +0,0 @@
|
||||
//go:generate stringer -type=NetworkType
|
||||
package net |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
zconfig "git.wegmueller.it/illumos/go-zone/config" |
||||
"git.wegmueller.it/opencloud/opencloud/config" |
||||
"github.com/spf13/viper" |
||||
"github.com/ztrue/tracerr" |
||||
) |
||||
|
||||
type NetworkType int |
||||
|
||||
const ( |
||||
NetworkTypeNat NetworkType = iota |
||||
NetworkTypeVXLAN |
||||
NetworkTypeVLAN |
||||
NetworkTypeDirect |
||||
NetworkTypeUnset = 999 |
||||
) |
||||
|
||||
type Network struct { |
||||
Type NetworkType `json:"type"` |
||||
Name string `json:"name"` |
||||
CIDR string `json:"cidr"` |
||||
PoolStart int `json:"pool_start"` |
||||
PoolEnd int `json:"pool_end"` |
||||
AddressLease []string `json:"address_lease"` |
||||
Physical string `json:"physical"` |
||||
DefaultRouter string `json:"defrouter"` |
||||
DNSOptions map[string]string `json:"dns_options"` |
||||
} |
||||
|
||||
func NewNetwork(name string) *Network { |
||||
return &Network{Name: name, AddressLease: make([]string, 0), PoolEnd: 254, PoolStart: 128, DNSOptions: make(map[string]string), Type: NetworkTypeUnset} |
||||
} |
||||
|
||||
func (n *Network) Save() error { |
||||
if n.Physical == "" || n.Type == NetworkTypeUnset { |
||||
return fmt.Errorf("network configuration not capable to be setup") |
||||
} |
||||
f, err := os.OpenFile(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net", n.Name+".json"), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
defer f.Close() |
||||
if err := json.NewEncoder(f).Encode(n); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (n *Network) Load() error { |
||||
f, err := os.Open(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net", n.Name+".json")) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
defer f.Close() |
||||
if err := json.NewDecoder(f).Decode(n); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (n *Network) SetupZone(z *zconfig.Zone) error { |
||||
switch n.Type { |
||||
case NetworkTypeDirect: |
||||
return setupDirectNetworking(n, z) |
||||
case NetworkTypeNat: |
||||
return setupNatNetworing(n, z) |
||||
case NetworkTypeVXLAN: |
||||
return setupVXLANNetworking(n, z) |
||||
case NetworkTypeVLAN: |
||||
return setupVLANNetworking(n, z) |
||||
default: |
||||
return fmt.Errorf("network Type %s is unknown", n.Type) |
||||
} |
||||
} |
||||
|
||||
func CreateNetwork(name, addressPool, physical, defRouter string, netType NetworkType) (*Network, error) { |
||||
n := NewNetwork(name) |
||||
n.CIDR = addressPool |
||||
n.Physical = physical |
||||
n.DefaultRouter = defRouter |
||||
n.Type = netType |
||||
if err := n.Save(); err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
return n, nil |
||||
} |
||||
|
||||
func GetNetwork(name string) (*Network, error) { |
||||
n := &Network{Name: name} |
||||
if err := n.Load(); err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
return n, nil |
||||
} |
||||
|
||||
func ListNetworks() ([]*Network, error) { |
||||
matches, err := filepath.Glob(filepath.Join(viper.GetString(config.HostMountpointConfigKey), "net") + "/*.json") |
||||
if err != nil { |
||||
return nil, tracerr.Wrap(err) |
||||
} |
||||
n := make([]*Network, 0) |
||||
for _, m := range matches { |
||||
netw := Network{} |
||||
err := func() error { |
||||
f, err := os.Open(m) |
||||
if err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
defer f.Close() |
||||
if err := json.NewDecoder(f).Decode(&netw); err != nil { |
||||
return tracerr.Wrap(err) |
||||
} |
||||
return nil |
||||
}() |
||||
if err != nil { |
||||
continue |
||||
} |
||||
n = append(n, &netw) |
||||
} |
||||
return n, nil |
||||
} |