Skip to content

Commit

Permalink
instances/volumes (#1427)
Browse files Browse the repository at this point in the history
* instances/volumes

* .
  • Loading branch information
eyberg authored Feb 8, 2023
1 parent 07d82d0 commit 6005003
Show file tree
Hide file tree
Showing 12 changed files with 458 additions and 113 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,18 @@ test: post-test

generate:
buf generate --path ./protos/imageservice/imageservice.proto
buf generate --path ./protos/instanceservice/instanceservice.proto
buf generate --path ./protos/volumeservice/volumeservice.proto

clean:
$(GOCLEAN)
rm -f $(BINARY_NAME)
rm -rf protos/imageservice/*.go
rm -rf protos/imageservice/*.json
rm -rf protos/instanceservice/*.go
rm -rf protos/instanceservice/*.json
rm -rf protos/volumeservice/*.go
rm -rf protos/volumeservice/*.json

run:
$(GOBUILD) -o $(BINARY_NAME) -v .
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ You can find more examples and tutorial on youtube as well:

[https://www.youtube.com/channel/UC3mqDqCVu3moVKzmP2YNmlg](https://www.youtube.com/channel/UC3mqDqCVu3moVKzmP2YNmlg)

## Daemon

OPS started out as a daemon-less cli tool to build and run unikernels
locally and to also interact with the various clouds. We will keep that
functionality as-is, however, ops can also run as a daemon locally for
software that is a composition of multiple services. The daemon expects
to have elevated privileges (currently via suid bit) in order to place
the various programs on their class c network (vs relying on user-mode).
This is not necessary for 'ops run', 'ops pkg load' or 'ops instance
create' but only for multipl services ran locally that expect to
communicate to each other vs just the host.

For now the daemon and 'ops instance create' share metadata but that is
expected to change in the future.

## Apple M1/M2 Users

The Apple M1 and M2 are ARM based. OPS is built for users primarily
Expand Down
102 changes: 2 additions & 100 deletions cmd/cmd_daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@ package cmd
import (
"fmt"

"context"
"log"
"net"
"net/http"
"os"
"github.com/nanovms/ops/daemon"

api "github.com/nanovms/ops/lepton"
"github.com/nanovms/ops/provider"
"github.com/nanovms/ops/types"

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/nanovms/ops/protos/imageservice"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

// DaemonizeCommand turns ops into a daemon
Expand All @@ -31,97 +19,11 @@ func DaemonizeCommand() *cobra.Command {
return cmdDaemonize
}

// prob belongs in a root grpc-server folder
// not in the cmds folder
type server struct{}

func (*server) GetImages(_ context.Context, in *imageservice.ImageListRequest) (*imageservice.ImagesResponse, error) {

// stubbed for now - could conceivablly store creds in server and
// target any provider which would be nice
c := &types.Config{}
pc := &types.ProviderConfig{}

p, err := provider.CloudProvider("onprem", pc)
if err != nil {
fmt.Println(err)
}

ctx := api.NewContext(c)
images, err := p.GetImages(ctx)
if err != nil {
return nil, err
}

pb := &imageservice.ImagesResponse{
Count: int32(len(images)),
}

for i := 0; i < len(images); i++ {
img := &imageservice.Image{
Name: images[i].Name,
Path: images[i].Path,
Size: images[i].Size,
Created: images[i].Created.String(),
}

pb.Images = append(pb.Images, img)
}

return pb, nil
}

func daemonizeCommandHandler(cmd *cobra.Command, args []string) {
fmt.Println("Note: If on a mac this expects ops to have suid bit set for networking.")
fmt.Println("if you used the installer you are set otherwise run the following command\n" +
"\tsudo chown -R root /usr/local/bin/qemu-system-x86_64\n" +
"\tsudo chmod u+s /usr/local/bin/qemu-system-x86_64")

lis, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println(err)
os.Exit(1)
}

s := grpc.NewServer()
imageservice.RegisterImagesServer(s, &server{})
log.Println("Serving gRPC on 0.0.0.0:8080")
go func() {
err := s.Serve(lis)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}()

conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:8080",
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

gwmux := runtime.NewServeMux()
err = imageservice.RegisterImagesHandler(context.Background(), gwmux, conn)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}

log.Println("Serving json on http://0.0.0.0:8090")
fmt.Println("try issuing a request:\tcurl -XGET -k http://localhost:8090/v1/images | jq")
err = gwServer.ListenAndServe()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
daemon.Daemonize()
}
213 changes: 213 additions & 0 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package daemon

import (
"fmt"

"context"
"log"
"net"
"net/http"
"os"

api "github.com/nanovms/ops/lepton"
"github.com/nanovms/ops/provider"
"github.com/nanovms/ops/types"

"github.com/nanovms/ops/protos/imageservice"
"github.com/nanovms/ops/protos/instanceservice"
"github.com/nanovms/ops/protos/volumeservice"

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

type server struct{}

func (*server) GetInstances(_ context.Context, in *instanceservice.InstanceListRequest) (*instanceservice.InstancesResponse, error) {
// stubbed for now - could conceivablly store creds in server and
// target any provider which would be nice
c := &types.Config{}
pc := &types.ProviderConfig{}

p, err := provider.CloudProvider("onprem", pc)
if err != nil {
fmt.Println(err)
}

// for now we read from old 'ops run' output stored in
// ~/.ops/instances/{pid} but can def. re-factor to be in-mem for
// future since this is coming from daemon
ctx := api.NewContext(c)
instances, err := p.GetInstances(ctx)
if err != nil {
return nil, err
}

pb := &instanceservice.InstancesResponse{
Count: int32(len(instances)),
}

for i := 0; i < len(instances); i++ {
instance := &instanceservice.Instance{
Name: instances[i].Name,
Image: instances[i].Image,
Pid: instances[i].ID,
Status: instances[i].Status,
PrivateIp: instances[i].PrivateIps[0],
Created: instances[i].Created,
}

pb.Instances = append(pb.Instances, instance)
}

return pb, nil
}

func (*server) GetImages(_ context.Context, in *imageservice.ImageListRequest) (*imageservice.ImagesResponse, error) {

// stubbed for now - could conceivablly store creds in server and
// target any provider which would be nice
c := &types.Config{}
pc := &types.ProviderConfig{}

p, err := provider.CloudProvider("onprem", pc)
if err != nil {
fmt.Println(err)
}

ctx := api.NewContext(c)
images, err := p.GetImages(ctx)
if err != nil {
return nil, err
}

pb := &imageservice.ImagesResponse{
Count: int32(len(images)),
}

for i := 0; i < len(images); i++ {
img := &imageservice.Image{
Name: images[i].Name,
Path: images[i].Path,
Size: images[i].Size,
Created: images[i].Created.String(),
}

pb.Images = append(pb.Images, img)
}

return pb, nil
}

func (*server) GetVolumes(_ context.Context, in *volumeservice.VolumeListRequest) (*volumeservice.VolumesResponse, error) {

// stubbed for now - could conceivablly store creds in server and
// target any provider which would be nice
c := &types.Config{}
pc := &types.ProviderConfig{}

p, err := provider.CloudProvider("onprem", pc)
if err != nil {
fmt.Println(err)
}

ctx := api.NewContext(c)
// no clue why this is passed around like this
ctx.Config().VolumesDir = api.LocalVolumeDir

volumes, err := p.GetAllVolumes(ctx)
if err != nil {
return nil, err
}

rvols := *volumes

pb := &volumeservice.VolumesResponse{
Count: int32(len(rvols)),
}

for i := 0; i < len(rvols); i++ {
if err != nil {
return nil, err
}

vol := &volumeservice.Volume{
Name: rvols[i].Name,
Path: rvols[i].Path,
Size: rvols[i].Size, // unfort this has extra meta such as 'mb'
Created: rvols[i].CreatedAt,
}

pb.Volumes = append(pb.Volumes, vol)
}

return pb, nil
}

// Daemonize starts a grpc server along with a json frontend to interact
// with local/'onprem' installations.
func Daemonize() {
lis, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println(err)
os.Exit(1)
}

s := grpc.NewServer()
imageservice.RegisterImagesServer(s, &server{})
instanceservice.RegisterInstancesServer(s, &server{})
volumeservice.RegisterVolumesServer(s, &server{})

log.Println("Serving gRPC on 0.0.0.0:8080")
go func() {
err := s.Serve(lis)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}()

conn, err := grpc.DialContext(
context.Background(),
"0.0.0.0:8080",
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

gwmux := runtime.NewServeMux()
err = imageservice.RegisterImagesHandler(context.Background(), gwmux, conn)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

err = instanceservice.RegisterInstancesHandler(context.Background(), gwmux, conn)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

err = volumeservice.RegisterVolumesHandler(context.Background(), gwmux, conn)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

gwServer := &http.Server{
Addr: ":8090",
Handler: gwmux,
}

log.Println("Serving json on http://0.0.0.0:8090")
fmt.Println("try issuing a request:\tcurl -XGET -k http://localhost:8090/v1/images | jq")
err = gwServer.ListenAndServe()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
1 change: 1 addition & 0 deletions lepton/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type CloudImage struct {

// CloudInstance represents the instance that widely use in different
// Cloud Providers.
// mainly used for formatting standard response from any cloud provider
type CloudInstance struct {
ID string
Name string
Expand Down
2 changes: 1 addition & 1 deletion lepton/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type NanosVolume struct {
Name string `json:"name"`
Label string `json:"label"`
Data string `json:"data"`
Size string `json:"size"`
Size string `json:"size"` // this has extra meta in it that should be converted to just bytes
Path string `json:"path"`
AttachedTo string `json:"attached_to"`
CreatedAt string `json:"created_at"`
Expand Down
Loading

0 comments on commit 6005003

Please sign in to comment.