Browse Source

WIP Networking support

layerset
Till Wegmüller 4 years ago
parent
commit
6b5ba49a26
  1. 22
      Vagrantfile
  2. 8
      cmd/buildhelper.go
  3. 24
      cmd/imageadm/root.go
  4. 44
      cmd/podadm/network.go
  5. 27
      cmd/podadm/root.go
  6. 3
      config/host.go
  7. 1
      go.mod
  8. 2
      go.sum
  9. 64
      host/build.go
  10. 14
      host/container.go
  11. 46
      host/host.go
  12. 74
      host/network.go
  13. 55
      image/build/build.go
  14. 33
      net/direct.go
  15. 217
      net/net.go
  16. 129
      net/type.go
  17. 25
      pod/container.go
  18. 2
      pod/manifest.go
  19. 1
      pod/options.go

22
Vagrantfile vendored

@ -67,27 +67,43 @@ Vagrant.configure("2") do |config|
# documentation for more information about their specific syntax and use.
config.vm.provision "shell", inline: <<-SHELL
# Install the go compiler and system header files to compile the applications inside the zone
cat <<EOF >> ~/.bashrc
cat <<EOF >> ~/.bashrc
export PATH=/usr/local/bin:$PATH
EOF
zfs create -o mountpoint=/zones rpool/zones
cat <<EOF | zonecfg -z template
dladm create-vnic -l e1000g0 template0
cat <<EOF | zonecfg -z template
create
set zonepath=/zones/template
set ip-type=exclusive
add net
set physical=e1000g0
set physical=template0
set allowed-address=10.0.2.20/24
end
verify
commit
EOF
cat <<EOF > /etc/opencloud.yaml
root:
zfs:
path: "rpool/podhost"
mountpoint: "/podhost"
docker:
hubUrl: "https://registry-1.docker.io"
EOF
echo "Installing zone template"
zoneadm -z template install
echo "Installing developer tools"
pkg install -v golang-111 system/header developer/build/gnu-make git developer/gcc-8 system/library/gcc-8-runtime
echo "Updating system"
pkg update -v
SHELL
end

8
cmd/buildhelper.go

@ -3,11 +3,19 @@ package main
import (
"encoding/json"
"os"
"runtime"
"git.wegmueller.it/opencloud/opencloud/image/build"
"github.com/sirupsen/logrus"
)
func main() {
logrus.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
QuoteEmptyFields: true,
})
logrus.Infof("running buildhelper for os %s(%s)", runtime.GOOS, runtime.GOARCH)
var imageConfig build.Image
if err := json.NewDecoder(os.Stdin).Decode(&imageConfig); err != nil {
panic(err)

24
cmd/imageadm/root.go

@ -4,6 +4,7 @@ import (
"os"
"git.wegmueller.it/opencloud/opencloud/host"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ztrue/tracerr"
@ -30,13 +31,30 @@ var RootCmd = &cobra.Command{
}
logLevel := viper.GetString("loglevel")
debug := viper.GetBool("debug")
if logLevel != "" {
level := logrus.InfoLevel
switch logLevel {
case "emerg", "fatal":
level = logrus.FatalLevel
case "panic":
level = logrus.PanicLevel
case "error":
level = logrus.ErrorLevel
case "warn", "warning":
level = logrus.WarnLevel
case "info":
level = logrus.InfoLevel
case "debug":
level = logrus.DebugLevel
case "trace":
level = logrus.TraceLevel
}
if debug {
if debug {
level = logrus.DebugLevel
}
h = host.NewHost()
h.SetForegroundLogging()
logrus.SetLevel(level)
return nil
},
}

44
cmd/podadm/network.go

@ -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)
}

27
cmd/podadm/root.go

@ -4,6 +4,7 @@ import (
"os"
"git.wegmueller.it/opencloud/opencloud/host"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/ztrue/tracerr"
@ -16,7 +17,7 @@ var RootCmd = &cobra.Command{
Short: "Create and manage Containers on this system",
Long: `This is the Administrative Tool to Create and manage Containers on the system you are on
For now KVM and BHyve Containers are not supported but may be in the future.
For now KVM and BHyve Containers are not supported but should be in the future.
`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
viper.SetConfigName("opencloud")
@ -31,13 +32,31 @@ var RootCmd = &cobra.Command{
}
logLevel := viper.GetString("loglevel")
debug := viper.GetBool("debug")
if logLevel != "" {
level := logrus.InfoLevel
switch logLevel {
case "emerg", "fatal":
level = logrus.FatalLevel
case "panic":
level = logrus.PanicLevel
case "error":
level = logrus.ErrorLevel
case "warn", "warning":
level = logrus.WarnLevel
case "info":
level = logrus.InfoLevel
case "debug":
logrus.SetReportCaller(true)
level = logrus.DebugLevel
case "trace":
level = logrus.TraceLevel
}
if debug {
if debug {
level = logrus.DebugLevel
}
h = host.NewHost()
h.SetForegroundLogging()
logrus.SetLevel(level)
return nil
},
}

3
config/host.go

@ -3,4 +3,7 @@ package config
const (
HostDatasetConfigKey = "root.zfs.path"
HostMountpointConfigKey = "root.zfs.mountpoint"
HostDatasetOptionsKey = "root.zfs.options"
HostImagesOptionsKey = "root.zfs.image_options"
HostPodsOptionsKey = "root.zfs.pod_options"
)

1
go.mod

@ -38,6 +38,7 @@ require (
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/pkg/errors v0.8.1
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.4.1
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cast v1.3.0 // indirect
github.com/spf13/cobra v0.0.3

2
go.sum

@ -354,6 +354,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools v2.2.0+incompatible h1:y0IMTfclpMdsdIbr6uwmJn5/WZ7vFuObxDMdrylFM3A=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

64
host/build.go

@ -11,9 +11,12 @@ import (
"git.wegmueller.it/opencloud/opencloud/image/build"
"git.wegmueller.it/opencloud/opencloud/image/reference"
namesgenerator "git.wegmueller.it/opencloud/opencloud/namegenerator"
opcNet "git.wegmueller.it/opencloud/opencloud/net"
"git.wegmueller.it/opencloud/opencloud/pod"
"github.com/docker/docker/pkg/fileutils"
"github.com/dustin/go-humanize"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/ztrue/tracerr"
)
@ -24,17 +27,22 @@ const helperPath = "/usr/lib/brand/pod/helpers/%s/" + buildHelperName
func (h *Host) RunBuild(cfg *build.ImageConfig) error {
buildImages := make(map[string]*image.Image)
for _, imgCfg := range cfg.Images {
logrus.Infof("starting image build for %s", imgCfg.Name)
if cfg.Maintainer != nil {
imgCfg.Maintainer = cfg.Maintainer
}
nameRef := reference.NewReferenceString(imgCfg.Name)
container, err := h.CreateBuildContainer(imgCfg)
logrus.Tracef("name reference for new image %s", nameRef)
opts := pod.Options{Network: opcNet.DefaultNetworkName}
container, err := h.CreateBuildContainer(imgCfg, &opts)
if err != nil {
return tracerr.Wrap(err)
}
logrus.Infof("using container %s(%s) for build", container.Name, container.UUID)
// Run all copy operations to get any files from other images the we might need
for _, op := range imgCfg.CopyOperations {
logrus.Debugf("copying %s from source Image %s", op.Name, op.SourceImage)
srcRefString := reference.NewReferenceString(op.SourceImage)
srcImg, ok := buildImages[srcRefString.String()]
if !ok {
@ -56,15 +64,18 @@ func (h *Host) RunBuild(cfg *build.ImageConfig) error {
}
//Boot Container
logrus.Infof("starting build container %s(%s)", container.UUID, container.Name)
if err = container.Run(); err != nil {
return tracerr.Wrap(err)
}
// Execute the Build Operations
logrus.Infof("running buildhelper inside %s(%s)", container.UUID, container.Name)
if err = h.RunBuildHelperInContainer(imgCfg, container); err != nil {
return tracerr.Wrap(err)
}
logrus.Debugf("stopping container %s(%s)", container.UUID, container.Name)
if err = container.Kill(); err != nil {
return tracerr.Wrap(err)
}
@ -73,7 +84,9 @@ func (h *Host) RunBuild(cfg *build.ImageConfig) error {
if resultImg, err = h.ExportToImage(nameRef, container, imgCfg); err != nil {
return tracerr.Wrap(err)
}
logrus.Infof("exported into image %s", resultImg.Name)
logrus.Debugf("cleaning up container %s(%s)", container.UUID, container.Name)
if err = container.Destroy(); err != nil {
return tracerr.Wrap(err)
}
@ -83,13 +96,14 @@ func (h *Host) RunBuild(cfg *build.ImageConfig) error {
return nil
}
func (h *Host) CreateBuildContainer(config build.Image) (*pod.Container, error) {
func (h *Host) CreateBuildContainer(config build.Image, options *pod.Options) (*pod.Container, error) {
name := namesgenerator.GetRandomName(5)
ref := reference.NewReferenceString(config.BaseImage)
logrus.Debugf("creating build container for %s", config.Name)
// Either create a container from a base image
if config.BaseImage != image.EmptyImageName {
logrus.Debugf("starting from base image %s", config.BaseImage)
// Load base image
img, err := image.FindImage(ref)
if err != nil {
@ -103,18 +117,42 @@ func (h *Host) CreateBuildContainer(config build.Image) (*pod.Container, error)
if err != nil {
return nil, tracerr.Wrap(err)
}
net, err := opcNet.GetNetwork(options.Network)
if err != nil {
return nil, tracerr.Wrap(err)
}
anet, err := net.SetupNic()
if err != nil {
return nil, tracerr.Wrap(err)
}
pm.Solaris.Anet = []specs.SolarisAnet{anet}
container, err := pod.CreateContainer(h.Dataset, pm, name, ref.Tag, img)
if err != nil {
return nil, tracerr.Wrap(err)
}
if err = h.setupDNSInZone(container, net); err != nil {
return nil, tracerr.Wrap(err)
}
logrus.Debugf("build container %s(%s) created", container.Name, container.UUID)
return container, nil
} else {
//or make an new empty one if the base image is image.EmptyImageName
logrus.Debug("creating an empty container")
pm := pod.EmptyContainerManifest()
net, err := opcNet.GetNetwork(options.Network)
if err != nil {
return nil, tracerr.Wrap(err)
}
anet, err := net.SetupNic()
if err != nil {
return nil, tracerr.Wrap(err)
}
pm.Solaris.Anet = []specs.SolarisAnet{anet}
container, err := pod.CreateEmptyContainer(h.Dataset, pm, name)
if err != nil {
return nil, tracerr.Wrap(err)
}
logrus.Debugf("build container %s(%s) created", container.Name, container.UUID)
return container, nil
}
@ -182,6 +220,7 @@ func (h *Host) ExportToImage(imageName reference.Reference, container *pod.Conta
//First lets search if we already have the same image with a new tag.
var i *image.Image
if img, err := h.GetLocalImage(imageName); err != nil {
logrus.Debugf("no local image for %s exists create new empty one", imageName)
if !image.IsNotExistError(err) {
return nil, tracerr.Wrap(err)
}
@ -189,10 +228,12 @@ func (h *Host) ExportToImage(imageName reference.Reference, container *pod.Conta
return nil, tracerr.Wrap(err)
}
} else {
logrus.Debugf("using already existing image %s", imageName)
i = img
}
//Create a OCI Image Writer
logrus.Debug("Creating OCI Image for %s", imageName)
wr, err := i.NewOCIWriter()
if err != nil {
return nil, tracerr.Wrap(err)
@ -204,36 +245,43 @@ func (h *Host) ExportToImage(imageName reference.Reference, container *pod.Conta
//Get Parent image and top layer
if imageConfig.BaseImage == "" || imageConfig.BaseImage == image.EmptyImageName {
logrus.Infof("creating base image from container %s", container.UUID)
if err = wr.AddTree(container.Path("root")); err != nil {
return nil, tracerr.Wrap(err)
}
logrus.Info("successful")
} else {
parentRef := reference.NewReferenceString(imageConfig.BaseImage)
logrus.Infof("Creating diff between %s and %s", parentRef, imageName)
parentImg, err := image.FindImage(parentRef)
if err != nil {
return nil, tracerr.Wrap(err)
}
tg := parentImg.Tags[parentRef.Tag]
topLayer := tg.Layers[len(tg.Layers)-1]
parentLayer := parentImg.GetLayerDS(topLayer.Digest)
if parentLayer == nil {
return nil, fmt.Errorf("could not find layer %s in image %s", topLayer.Digest, parentRef)
parentLayerDesc := tg.Layers[len(tg.Layers)-1]
parentLayerDataset := parentImg.GetLayerDS(parentLayerDesc.Digest)
logrus.Debugf("top most layer hash is %s", parentLayerDesc.Digest)
if parentLayerDataset == nil {
return nil, fmt.Errorf("could not find layer dataset %s in image %s", parentLayerDesc.Digest, parentRef)
}
err = wr.AddDiff(parentLayer.VFSPath(), container.Path("root"))
err = wr.AddDiff(parentLayerDataset.VFSPath(), container.Path("root"))
if err != nil {
return nil, tracerr.Wrap(err)
}
logrus.Info("successful")
}
if err = wr.Close(); err != nil {
return nil, tracerr.Wrap(err)
}
logrus.Info("importing OCI image into dataset")
if err = i.ImportOCILayers(); err != nil {
return nil, tracerr.Wrap(err)
}
logrus.Info("successful")
return i, nil
}

14
host/container.go

@ -7,8 +7,10 @@ import (
"git.wegmueller.it/opencloud/opencloud/image"
"git.wegmueller.it/opencloud/opencloud/image/reference"
opcNet "git.wegmueller.it/opencloud/opencloud/net"
"git.wegmueller.it/opencloud/opencloud/pod"
"github.com/labstack/gommon/log"
"github.com/opencontainers/runtime-spec/specs-go"
uuid "github.com/satori/go.uuid"
"github.com/ztrue/tracerr"
)
@ -55,12 +57,22 @@ func (h *Host) CreateContainer(ref reference.Reference, opt *pod.Options) (*pod.
pm.Annotations[k] = v
}
net, err := opcNet.GetNetwork(opt.Network)
if err != nil {
return nil, tracerr.Wrap(err)
}
anet, err := net.SetupNic()
if err != nil {
return nil, tracerr.Wrap(err)
}
pm.Solaris.Anet = []specs.SolarisAnet{anet}
container, err := pod.CreateContainer(h.Dataset, pm, opt.Name, ref.Tag, img)
if err != nil {
return nil, tracerr.Wrap(err)
}
if err = container.Save(); err != nil {
if err = h.setupDNSInZone(container, net); err != nil {
return nil, tracerr.Wrap(err)
}

46
host/host.go

@ -4,18 +4,21 @@ import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
"time"
"git.wegmueller.it/opencloud/opencloud/config"
opcNet "git.wegmueller.it/opencloud/opencloud/net"
"git.wegmueller.it/opencloud/opencloud/pod"
"git.wegmueller.it/opencloud/opencloud/zfs"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"github.com/ztrue/tracerr"
)
type Host struct {
Dataset *zfs.Dataset
Dataset *zfs.Dataset
podStatusCache map[string]pod.Status
cacheDate time.Time
}
@ -23,7 +26,7 @@ type Host struct {
func NewHost() *Host {
h := Host{}
if ds, err := zfs.OpenDataset(viper.GetString("root.zfs.path")); err != nil {
if ds, err := zfs.OpenDataset(viper.GetString(config.HostDatasetConfigKey)); err != nil {
return &h
} else {
h.Dataset = ds
@ -32,12 +35,27 @@ func NewHost() *Host {
return &h
}
func (h *Host) SetForegroundLogging() {
logrus.SetFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
QuoteEmptyFields: true,
})
}
func (h *Host) SetBackgroundLogging() {
logrus.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
DisableColors: true,
QuoteEmptyFields: true,
})
}
func (h *Host) Initialize() error {
if h.Dataset != nil {
return fmt.Errorf("host already initialized")
}
mntPoint := viper.GetString("root.zfs.mountpoint")
mntPoint := viper.GetString(config.HostMountpointConfigKey)
if mntPoint != "" {
if err := os.MkdirAll(mntPoint, 0755); err != nil {
@ -45,8 +63,8 @@ func (h *Host) Initialize() error {
}
}
dsName := viper.GetString("root.zfs.path")
dsOptions := viper.GetStringMapString("root.zfs.options")
dsName := viper.GetString(config.HostDatasetConfigKey)
dsOptions := viper.GetStringMapString(config.HostDatasetOptionsKey)
dsOptions[zfs.PropertyMountpoint] = mntPoint
if ds, err := zfs.CreateDataset(dsName, zfs.DatasetTypeFilesystem, zfs.Properties(dsOptions)); err != nil {
@ -55,16 +73,28 @@ func (h *Host) Initialize() error {
h.Dataset = ds
}
dsOptions = viper.GetStringMapString("root.zfs.image_options")
dsOptions = viper.GetStringMapString(config.HostImagesOptionsKey)
if _, err := h.Dataset.CreateChildDataset("images", zfs.Properties(dsOptions)); err != nil {
return tracerr.Wrap(err)
}
dsOptions = viper.GetStringMapString("root.zfs.pod_options")
dsOptions = viper.GetStringMapString(config.HostPodsOptionsKey)
if _, err := h.Dataset.CreateChildDataset("pods", zfs.Properties(dsOptions)); err != nil {
return tracerr.Wrap(err)
}
if err := os.MkdirAll(filepath.Join(mntPoint, "net"), 0755); err != nil {
return tracerr.Wrap(err)
}
//TODO choose first link detected as UP
//TODO pick ip range of that nic previously detected
//TODO pick router the same way as ip
_, err := opcNet.CreateNetwork("default", "10.0.2.0/24", "e1000g0", "10.0.2.2", []string{"nameserver=9.9.9.9", "nameserver=149.112.112.112"}, opcNet.NetworkTypeDirect)
if err != nil {
return tracerr.Wrap(err)
}
return nil
}

74
host/network.go

@ -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
}

55
image/build/build.go

@ -12,7 +12,7 @@ import (
"git.wegmueller.it/opencloud/opencloud/image/oci"
"github.com/docker/docker/pkg/fileutils"
"github.com/mholt/archiver"
"github.com/sirupsen/logrus"
"github.com/ztrue/tracerr"
)
@ -30,12 +30,25 @@ func InstallSourcesInsideZone(config Image) error {
}
if src.Target[len(src.Target)-1] == '/' || src.IsArchive {
if err = archiver.Unarchive(absSRC, src.Target); err != nil {
logrus.Infof("Extracting archive %s to %s", src.Name, src.Target)
compressor := oci.ArchiveCompressorNone
if strings.Contains(src.Source, "gz") {
compressor = oci.ArchiveCompressorGzip
}
rd, err := oci.NewTarReader(compressor, absSRC)
if err != nil {
return tracerr.Wrap(err)
}
if err = rd.ExtractTreeInto(src.Target); err != nil {
return tracerr.Wrap(err)
}
if err = rd.Close(); err != nil {
return tracerr.Wrap(err)
}
if err = os.Remove(src.Source); err != nil {
return tracerr.Wrap(err)
}
logrus.Info("successful")
}
case SourceTypeHttp:
f := func(src SourceDescription) error {
@ -57,7 +70,19 @@ func InstallSourcesInsideZone(config Image) error {
}
if src.IsArchive {
if err = archiver.Unarchive(tmpFile.Name(), src.Target); err != nil {
logrus.Infof("Extracting archive into %s", src.Target)
compressor := oci.ArchiveCompressorNone
if strings.Contains(src.Source, "gz") {
compressor = oci.ArchiveCompressorGzip
}
rd, err := oci.NewTarReader(compressor, tmpFile.Name())
if err != nil {
return tracerr.Wrap(err)
}
if err = rd.ExtractTreeInto(src.Target); err != nil {
return tracerr.Wrap(err)
}
if err = rd.Close(); err != nil {
return tracerr.Wrap(err)
}
} else {
@ -67,25 +92,30 @@ func InstallSourcesInsideZone(config Image) error {
}
return nil
}
logrus.Infof("Downloading http(s) source %s from %s", src.Name, src.Source)
if err := f(src); err != nil {
return err
}
logrus.Info("successful")
case SourceTypeGit:
git, err := exec.LookPath("git")
if err != nil {
return tracerr.Wrap(err)
}
var repo, branch string
if idx := strings.Index(src.Target, "@"); idx >= 0 {
repo, branch = src.Target[:idx], src.Target[idx+1:]
if idx := strings.Index(src.Source, "@"); idx >= 0 {
repo, branch = src.Source[:idx], src.Source[idx+1:]
} else {
repo = src.Source
branch = "master"
}
logrus.Infof("Cloning git repo %s", src.Source)
gitCmd := exec.Command(git, "clone", "--single-branch", "-b", branch, repo, src.Target)
if err := gitCmd.Run(); err != nil {
if out, err := gitCmd.CombinedOutput(); err != nil {
logrus.Errorf("failed git clone %s", out)
return tracerr.Wrap(err)
}
logrus.Info("successful")
}
}
return nil
@ -93,8 +123,13 @@ func InstallSourcesInsideZone(config Image) error {
func CopySourcesIntoZone(config Image, zonepath string, allowLocal bool) error {
for i, src := range config.Sources {
if !allowLocal {
logrus.Debugf("local file source not allowed on this host ignoring Source %s", src.Name)
return nil
}
switch src.Type {
case SourceTypeFile:
logrus.Infof("Copying local file %s into zonepath %s", src.Name, zonepath)
absSRC, err := filepath.Abs(src.Source)
if err != nil {
return tracerr.Wrap(err)
@ -102,6 +137,7 @@ func CopySourcesIntoZone(config Image, zonepath string, allowLocal bool) error {
if src.Target[len(src.Target)-1] == '/' || src.IsArchive {
if src.IsRoot {
logrus.Infof("Extracting Rootfilesystem into %s, inside %s", src.Target, zonepath)
absTarget, err := filepath.Abs(zonepath + "/root/" + src.Target)
if err != nil {
return tracerr.Wrap(err)
@ -120,7 +156,9 @@ func CopySourcesIntoZone(config Image, zonepath string, allowLocal bool) error {
if err = rd.Close(); err != nil {
return tracerr.Wrap(err)
}
logrus.Info("successful")
} else {
logrus.Debugf("Copying archive %s into zonepath %s", src.Source, zonepath)
src.Source = fmt.Sprintf("/.sourceArchive.%d", i)
absTarget, err := filepath.Abs(zonepath + src.Source)
if err != nil {
@ -134,6 +172,7 @@ func CopySourcesIntoZone(config Image, zonepath string, allowLocal bool) error {
config.Sources[i] = src
}
} else {
logrus.Debugf("Copying file %s into zonepath %s", src.Source, zonepath)
absTarget, err := filepath.Abs(zonepath + "/" + src.Target)
if err != nil {
return tracerr.Wrap(err)
@ -178,6 +217,7 @@ func PerformActions(imageConfig Image) error {
if act.Provide {
continue
}
logrus.Infof("Performing %s", act.Name)
cmdArg := strings.Split(act.Command, " ")
cmdBin := cmdArg[0]
cmdArg = cmdArg[1:]
@ -198,9 +238,12 @@ func PerformActions(imageConfig Image) error {
}
}
//TODO implement user and group switching
logrus.Debugf("executing %s with %v", cmdBin, cmdArg)
if err := cmd.Run(); err != nil {
logrus.Error("failed")
return tracerr.Wrap(err)
}
logrus.Info("successful")
}
return nil
}

33
net/direct.go

@ -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
}

217
net/net.go

@ -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
}

129
net/type.go

@ -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
}

25
pod/container.go

@ -26,6 +26,8 @@ import (
const (
ROOTFSDatasetName = "root"
BrandBHyve = "bhyve"
BrandKVM = "kvm"
)
type Container struct {
@ -138,6 +140,16 @@ func CreateEmptyContainer(parentDataset *zfs.Dataset, pm *spec.Spec, name string
}
}
if pm.Solaris != nil {
for _, anet := range pm.Solaris.Anet {
container.Zone.Networks = append(container.Zone.Networks, config.Network{
AllowedAddress: anet.Allowedaddr,
Physical: anet.Linkname,
Defrouter: anet.Defrouter,
})
}
}
var lockFd *os.File
if lockFd, err = config.GrabZoneLockFile(*container.Zone); err != nil {
return nil, tracerr.Wrap(err)
@ -290,6 +302,19 @@ func CreateContainer(parentDataset *zfs.Dataset, pm *spec.Spec, name, tag string
}
}
if container.Manifest.Solaris != nil {
for _, anet := range container.Manifest.Solaris.Anet {
//TODO add multi network support
container.Zone.Networks = []config.Network{
{
AllowedAddress: anet.Allowedaddr,
Physical: anet.Linkname,
Defrouter: anet.Defrouter,
},
}
}
}
var lockFd *os.File
if lockFd, err = config.GrabZoneLockFile(*container.Zone); err != nil {
return nil, tracerr.Wrap(err)

2
pod/manifest.go

@ -61,6 +61,8 @@ func ContainerManifestFromImage(ref reference.Reference, img *image.Image) (pm *
},
)
}
pm.Solaris.Anet = []specs.SolarisAnet{}
pm.Process = &specs.Process{
User: specs.User{
Username: config.Config.User,

1
pod/options.go

@ -4,6 +4,7 @@ type Options struct {