├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── api ├── proto │ └── v1 │ │ ├── hyper.proto │ │ ├── l2.proto │ │ ├── l2Controller.proto │ │ ├── machinery.proto │ │ ├── volumes.proto │ │ └── vpc.proto └── swagger │ └── v1 │ ├── hyper.swagger.json │ ├── l2.swagger.json │ ├── l2Controller.swagger.json │ ├── machinery.swagger.json │ ├── volumes.swagger.json │ └── vpc.swagger.json ├── cmd ├── hyper │ └── main.go ├── l2agent │ └── main.go ├── l3agent │ └── main.go ├── sbs │ └── main.go └── vpc │ └── main.go ├── docs └── res │ └── vpc.jpg ├── go.mod ├── go.sum ├── pkg ├── api │ └── v1 │ │ ├── hyper │ │ ├── hyper.pb.go │ │ └── hyper_grpc.pb.go │ │ ├── l2 │ │ ├── l2.pb.go │ │ └── l2_grpc.pb.go │ │ ├── l2Controller │ │ ├── l2Controller.pb.go │ │ └── l2Controller_grpc.pb.go │ │ ├── machinery │ │ ├── machinery.pb.go │ │ └── machinery_grpc.pb.go │ │ ├── volumes │ │ └── volumes.pb.go │ │ └── vpc │ │ ├── vpc.pb.go │ │ └── vpc_grpc.pb.go ├── hyper │ ├── cloudinit.go │ ├── cloudinit_test.go │ ├── cmd │ │ ├── cmd.go │ │ └── list.go │ ├── disks.go │ ├── domain.go │ ├── domain_test.go │ ├── hyper.go │ ├── libvirt.go │ ├── power.go │ ├── stats.go │ ├── templates.go │ ├── xmddesc_test.go │ └── xmldesc.go ├── l2 │ ├── api.go │ ├── bridge.go │ ├── cmd │ │ ├── cmd.go │ │ ├── serve.go │ │ └── watch.go │ ├── config.go │ ├── controller │ │ ├── bgp │ │ │ ├── README.md │ │ │ └── bgp.go │ │ ├── controller.go │ │ └── nullController.go │ ├── gc.go │ ├── miss.go │ ├── nic.go │ ├── pprof.go │ ├── stack.go │ ├── transport │ │ ├── tap │ │ │ ├── arp.go │ │ │ ├── arp_test.go │ │ │ ├── fdb.go │ │ │ ├── fdb_test.go │ │ │ ├── icmp6.go │ │ │ ├── icmp6_test.go │ │ │ ├── linux.go │ │ │ ├── listener.go │ │ │ ├── listener_test.go │ │ │ ├── protocol │ │ │ │ ├── protocol.go │ │ │ │ ├── quic │ │ │ │ │ ├── frames.go │ │ │ │ │ ├── quic.go │ │ │ │ │ └── quic_test.go │ │ │ │ ├── vpctp │ │ │ │ │ ├── ebpf.go │ │ │ │ │ ├── vpctp.go │ │ │ │ │ ├── vpctp_test.go │ │ │ │ │ └── xdp │ │ │ │ │ │ ├── Makefile │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── bpf_helper_defs.h │ │ │ │ │ │ ├── bpf_helpers.h │ │ │ │ │ │ ├── types.h │ │ │ │ │ │ ├── vpc_kern.c │ │ │ │ │ │ └── vpc_kern.h │ │ │ │ └── vxlan │ │ │ │ │ ├── hash.go │ │ │ │ │ ├── packet.go │ │ │ │ │ ├── packet_test.go │ │ │ │ │ ├── vxlan.go │ │ │ │ │ └── vxlan_test.go │ │ │ ├── tap.go │ │ │ ├── tap_test.go │ │ │ └── vlan.go │ │ ├── transport.go │ │ └── vtep │ │ │ └── vtep.go │ └── xdp │ │ ├── xdp.go │ │ └── xdptap.go ├── l3 │ ├── bgp.go │ ├── cmd │ │ ├── cmd.go │ │ └── create.go │ ├── config.go │ ├── dhcp.go │ ├── l2.go │ ├── netfilter.go │ ├── ns │ │ └── netns.go │ └── router.go ├── sbs │ ├── blockCache.go │ ├── blockCache_test.go │ ├── blockStore.go │ ├── blockStore_test.go │ ├── cmd │ │ ├── agent.go │ │ ├── cmd.go │ │ └── start.go │ ├── config │ │ └── config.go │ ├── control.go │ ├── control │ │ ├── InMemController.go │ │ └── controller.go │ ├── control_test.go │ ├── errors.go │ ├── listen.go │ ├── nbd.go │ ├── nbdProto.go │ ├── nbdProto.md │ ├── nbdSession.go │ ├── ndbSession_test.go │ ├── peers.go │ ├── raft.go │ ├── raftStores.go │ ├── raftStores_test.go │ ├── raft_test.go │ ├── rpc.go │ ├── rpc_test.go │ ├── server.go │ ├── stats.go │ ├── test_helpers.go │ └── volumes.go ├── utils │ ├── logs.go │ ├── machinery.go │ └── utils.go └── vpc │ ├── api.go │ ├── cmd │ ├── cmd.go │ ├── migrate.go │ └── serve.go │ ├── db.go │ └── migrations │ ├── 201911210823_create_vpc.sql │ └── 201911211000_create_subnet.sql └── scripts └── protoc-gen.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | #Ignore object files 15 | *.o 16 | 17 | build/* 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tom Worrall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | FLAGS=-ldflags="-w -s" 2 | GO=go 3 | GO115=go1.15beta1 4 | GO_BUILD=${GO} build ${FLAGS} 5 | GO115_BUILD=${GO115} build ${FLAGS} 6 | 7 | all: l2 l3 vpc sbs 8 | 115: l2_go115 l3_go115 sbs_go115 vpc 9 | 10 | .PHONY: l3 11 | l3: 12 | ${GO_BUILD} -o ./build/l3-agent ./cmd/l3agent 13 | 14 | l3_go115: 15 | ${GO115_BUILD} -o ./build/l3-agent ./cmd/l3agent 16 | 17 | .PHONY: l2 18 | l2: 19 | ${GO_BUILD} -o ./build/l2-agent ./cmd/l2agent 20 | 21 | l2_go115: 22 | ${GO115_BUILD} -o ./build/l2-agent ./cmd/l2agent 23 | 24 | sbs_go115: 25 | ${GO115_BUILD} -o ./build/sbs ./cmd/sbs 26 | 27 | .PHONY: vpc 28 | vpc: 29 | ${GO_BUILD} -o ./build/vpc ./cmd/vpc 30 | 31 | .PHONY: sbs 32 | sbs: 33 | ${GO_BUILD} -o ./build/sbs ./cmd/sbs 34 | 35 | .PHONY: strip 36 | strip: 37 | strip --strip-unneeded ./build/l2-agent 38 | strip --strip-unneeded ./build/l3-agent 39 | strip --strip-unneeded ./build/vpc 40 | strip --strip-unneeded ./build/sbs 41 | 42 | protos: 43 | ./scripts/protoc-gen.sh 44 | 45 | clean: 46 | rm -f ./build/l3-agent 47 | rm -f ./build/l2-agent 48 | rm -f ./build/vpc 49 | rm -f ./build/sbs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

VPC

3 | Create simple VxLAN based VPC's written in (mostly) Go using Linux bridges/netlink, iptables & network namespaces. 4 |

5 | Go Report Card 6 |

7 |
8 |
9 | 10 | ## Why? 11 | For fun and to learn! 12 | 13 | # Schematic 14 | ![vpc](./docs/res/vpc.jpg "VPC") 15 | 16 | ## Host Types 17 | - Compute hosts facilitate the creation and management of VM's or containers 18 | - Route hosts provide virtual routers 19 | - Management hosts (not in diagram) provide management facilities such as BGP route reflection and configuration management. 20 | 21 | > There is no technical reason why a compute host can also be a route host and vice-versa. This simply provides better security, bandwidth and segregation of duties on hosts. 22 | 23 | ## Connectivity 24 | Each VM, namespace or container is connected to a Linux bridge (with VLAN filtering enabled) on a compute host. Compute hosts are connected via Linux VxLAN devices (VTEPs). 25 | 26 | ## VTEP Learning 27 | VxLAN learning is disabled by default. Learning is derived from an ML-BGP-L2VPN-EVPN client (via [frr](https://github.com/FRRouting/frr)) on each compute host and route reflectors on management hosts. 28 | 29 | ## Segregation 30 | Each 'tenant' is separated by VxLAN VNI's and each Subnet is protected via inner VLAN tagging on a Linux bridge per tenant. 31 | 32 | # Agents 33 | ## L2 34 | The L2 agent provides a GRPC API to create bridges, VxLAN VTEPs and manage VLAN tagging on the bridges. 35 | 36 | ### Transports 37 | Can set up to use a linux VxLAN device, or use a TAP device with VxLAN encapsulation. The TAP device allows easier handling of ARP/ICMPv6 soliciations in the future. 38 | 39 | ## L3 40 | The L3 agent provides the functionality to create the virtual router namespaces and provide simple DHCP/NAT & routing capabilities. 41 | 42 | ## SBS 43 | Simple block storage - raft based replicated block storage medium exposing NBD endpoints 44 | 45 | # Similar architectures 46 | [Openstacks Neutron](https://wiki.openstack.org/wiki/Neutron) in Linux bridge mode. 47 | -------------------------------------------------------------------------------- /api/proto/v1/hyper.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package vpc.hyper; 4 | option go_package = ".;hyper"; 5 | 6 | enum VMStatus { 7 | PENDING = 0; 8 | CREATING = 1; 9 | AVAILABLE = 2; 10 | DELETING = 3; 11 | DELETED = 4; 12 | 13 | ERROR = 99; 14 | } 15 | 16 | message VM { 17 | int32 account = 1; 18 | string id = 2; 19 | VMStatus status = 17; 20 | map metadata = 4; 21 | 22 | string template_id = 3; 23 | string placement_id = 5; 24 | string hyper_id = 6; 25 | string subnet_id = 7; 26 | 27 | repeated StorageDevice storage = 8; 28 | repeated string fw_rule_sets = 9; 29 | repeated string nics = 10; 30 | 31 | PowerState power_state = 11; 32 | string power_state_last_update = 12; 33 | 34 | PowerState desired_power_state = 13; 35 | string desired_power_state_last_update = 14; 36 | 37 | string ssh_key_ref = 15; 38 | bytes user_data = 16; 39 | 40 | string created_at = 18; 41 | string updated_at = 19; 42 | string availability_area = 20; 43 | 44 | repeated VolumeAttachment volumes = 21; 45 | } 46 | 47 | enum PowerState { 48 | NONE = 0; 49 | RUNNING = 1; 50 | SHUTDOWN = 4; 51 | SHUTOFF = 5; 52 | CRASHED = 6; 53 | MIGRATING = 7; 54 | } 55 | 56 | message VMTemplate { 57 | string id = 1; 58 | int32 vcpu = 2; 59 | int64 ram = 3; 60 | string libvirt_xml = 4; 61 | } 62 | 63 | message VolumeAttachment { 64 | string volume = 1; 65 | string dev = 2; 66 | } 67 | 68 | message StorageDevice { 69 | string id = 1; 70 | string device = 2; 71 | 72 | enum Driver { 73 | LOCAL = 0; 74 | RDB = 1; 75 | } 76 | 77 | Driver driver = 3; 78 | int64 size = 4; 79 | } 80 | 81 | message StatsRequest { 82 | string id = 1; 83 | } 84 | 85 | message VCPUStats { 86 | uint32 id = 1; 87 | uint64 time = 2; 88 | float usage = 3; 89 | } 90 | 91 | message MemStats { 92 | uint64 majors_faults = 1; 93 | uint64 minor_faults = 2; 94 | 95 | uint64 unused = 3; 96 | uint64 available = 4; 97 | uint64 usable = 5; 98 | 99 | uint64 swap_in = 6; 100 | uint64 swap_out = 7; 101 | 102 | uint64 last_update = 8; 103 | } 104 | 105 | message NetStats { 106 | string id = 1; 107 | 108 | uint64 rx_bytes = 2; 109 | uint64 rx_pkts = 3; 110 | uint64 rx_errs = 4; 111 | uint64 rx_drops = 5; 112 | 113 | uint64 tx_bytes = 6; 114 | uint64 tx_pkts = 7; 115 | uint64 tx_errs = 8; 116 | uint64 tx_drops = 9; 117 | } 118 | 119 | message DiskStats { 120 | string id = 1; 121 | 122 | uint64 rd_reqs = 2; 123 | uint64 rd_bytes = 3; 124 | uint64 rd_times = 4; 125 | 126 | uint64 wr_reqs = 5; 127 | uint64 wr_bytes = 6; 128 | uint64 wr_times = 7; 129 | 130 | uint64 alloc = 8; 131 | uint64 cap = 9; 132 | uint64 phy = 10; 133 | } 134 | 135 | message VMStats { 136 | repeated VCPUStats vcpus = 1; 137 | repeated NetStats nets = 2; 138 | repeated DiskStats disks = 3; 139 | MemStats mem = 4; 140 | } 141 | 142 | message StatsResponse { 143 | repeated VMStats stats = 1; 144 | } 145 | 146 | message ListRequest { 147 | string id = 1; 148 | } 149 | 150 | message ListResponse { 151 | repeated VM vms = 1; 152 | } 153 | 154 | message PowerRequest { 155 | enum ForcePowerType { 156 | REBOOT = 0; 157 | SHUTDOWN = 1; 158 | FORCE_REBOOT = 2; 159 | FORCE_SHUTDOWN = 3; 160 | } 161 | 162 | string id = 1; 163 | ForcePowerType type = 2; 164 | } 165 | 166 | message PowerResponse {} 167 | 168 | service HyperService { 169 | rpc Stats(StatsRequest) returns (StatsResponse) {}; 170 | rpc List(ListRequest) returns (ListResponse) {}; 171 | rpc Power(PowerRequest) returns (PowerResponse) {}; 172 | } -------------------------------------------------------------------------------- /api/proto/v1/l2.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package vpc.l2; 4 | option go_package = ".;l2"; 5 | 6 | message StackRequest { 7 | int32 vpc_id = 1; 8 | } 9 | 10 | message Stack { 11 | int32 vpc_id = 1; 12 | string bridge_link_name = 2; 13 | int32 bridge_link_index = 3; 14 | } 15 | 16 | message StackResponse { 17 | Stack stack = 1; 18 | StackStatusResponse status = 2; 19 | } 20 | 21 | enum LinkStatus { 22 | DOWN = 0; 23 | UP = 1; 24 | MISSING = 2; 25 | } 26 | 27 | message StackStatusResponse { 28 | LinkStatus bridge = 1; 29 | LinkStatus transport = 2; 30 | } 31 | 32 | message StackChange { 33 | int32 vpc_id = 1; 34 | string action = 2; 35 | StackStatusResponse status = 3; 36 | } 37 | 38 | message NicRequest { 39 | int32 vpc_id = 1; 40 | string id = 2; 41 | uint32 subnet_vlan_id = 3; 42 | bool manually_added = 4; 43 | string manual_hwaddr = 5; 44 | repeated string ip = 6; 45 | } 46 | 47 | message Nic { 48 | int32 vpc_id = 1; 49 | string hwaddr = 2; 50 | string id = 3; 51 | string name = 4; 52 | int32 index = 5; 53 | uint32 vlan = 6; 54 | repeated string ip = 7; 55 | } 56 | 57 | message NicStatusResponse { 58 | LinkStatus status = 1; 59 | } 60 | 61 | message Empty {} 62 | 63 | service L2Service { 64 | rpc AddStack(StackRequest) returns (StackResponse) {}; 65 | rpc GetStack(StackRequest) returns (StackResponse) {}; 66 | rpc StackStatus(StackRequest) returns (StackStatusResponse) {}; 67 | rpc DeleteStack(StackRequest) returns (Empty) {}; 68 | rpc WatchStacks(Empty) returns (stream StackChange) {}; 69 | 70 | rpc AddNIC(NicRequest) returns (Nic) {}; 71 | rpc DeleteNIC(Nic) returns (Empty) {}; 72 | rpc NICStatus(Nic) returns (NicStatusResponse) {}; 73 | // rpc UpdateNIC(NicRequest) returns (Nic) {}; 74 | } -------------------------------------------------------------------------------- /api/proto/v1/l2Controller.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package vpc.l2Controller; 4 | option go_package = ".;l2Controller"; 5 | 6 | message MACIPRequest { 7 | uint32 VNID = 1; 8 | uint32 VLAN = 2; 9 | string MAC = 3; 10 | string IP = 4; 11 | } 12 | 13 | message MACIPResp { 14 | } 15 | 16 | enum LookupType { 17 | MAC = 0; 18 | IP = 1; 19 | } 20 | 21 | message LookupRequest { 22 | LookupType LookupType = 1; 23 | 24 | uint32 VNID = 2; 25 | uint32 VLAN = 3; 26 | string MAC = 4; 27 | string IP = 5; 28 | } 29 | 30 | message LookupResponse { 31 | string MAC = 1; 32 | string IP = 2; 33 | } 34 | 35 | message VNIDRequest { 36 | uint32 VNID = 1; 37 | } 38 | 39 | message RegResponse {} 40 | 41 | message BroadcastEndpointResponse { 42 | repeated string IP = 1; 43 | } 44 | 45 | service ControllerService { 46 | rpc RegisterMacIP(MACIPRequest) returns (MACIPResp) {}; 47 | rpc DeregisterMacIP(MACIPRequest) returns (MACIPResp) {}; 48 | rpc LookupIP(LookupRequest) returns (LookupResponse) {}; 49 | rpc LookupMac(LookupRequest) returns (LookupResponse) {}; 50 | rpc BroadcastEndpoints(VNIDRequest) returns (BroadcastEndpointResponse) {}; 51 | rpc RegisterEP(VNIDRequest) returns (RegResponse) {}; 52 | rpc DeregisterEP(VNIDRequest) returns (RegResponse) {}; 53 | } -------------------------------------------------------------------------------- /api/proto/v1/machinery.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package vpc.machinery; 4 | option go_package = ".;machinery"; 5 | 6 | message Machine { 7 | string id = 1; 8 | 9 | enum Capability { 10 | ORCHEST = 0; 11 | COMPUTE = 1; 12 | NETWORK = 2; 13 | STORAGE = 3; 14 | } 15 | 16 | repeated Capability capabilities = 2; 17 | string last_checkin = 3; 18 | string availability_zone = 4; 19 | 20 | map versions = 5; 21 | map limits = 6; 22 | } 23 | 24 | enum DeviceType { 25 | ROUTER = 0; 26 | VM = 1; 27 | BLOCK = 2; 28 | } 29 | 30 | message Placement { 31 | string id = 1; 32 | string machine_id = 2; 33 | 34 | DeviceType device_type = 4; 35 | string device_id = 5; 36 | } 37 | 38 | message PlacementsRequest { 39 | string machine_id = 1; 40 | } 41 | 42 | message PlacementsResponse { 43 | repeated Placement placements = 1; 44 | repeated DeviceType types = 2; 45 | } 46 | 47 | message PingResponse {} 48 | 49 | message MachinesRequest {} 50 | 51 | message MachinesResponse { 52 | repeated Machine machines = 1; 53 | } 54 | 55 | enum PlacementAction { 56 | ASSIGN = 0; 57 | REPLACE = 1; 58 | UNASSIGN = 2; 59 | } 60 | 61 | message PlaceRequest { 62 | PlacementAction action = 1; 63 | Placement placement = 2; 64 | } 65 | 66 | message PlaceResponse {} 67 | 68 | service MachineryService { 69 | rpc Ping(Machine) returns (PingResponse) {}; 70 | rpc Placements(PlacementsRequest) returns (PlacementsResponse) {}; 71 | rpc Machines(MachinesRequest) returns (MachinesResponse) {}; 72 | rpc Place(PlaceRequest) returns (PlaceResponse) {}; 73 | } -------------------------------------------------------------------------------- /api/proto/v1/volumes.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package vpc.volumes; 4 | option go_package = ".;volumes"; 5 | 6 | enum VolumeStatus { 7 | PENDING = 0; 8 | CREATING = 1; 9 | AVAILABLE = 2; 10 | INUSE = 3; 11 | BACKINGUP = 4; 12 | RESTORING = 5; 13 | RESIZING = 6; 14 | DELETING = 7; 15 | DELETED = 8; 16 | 17 | ERROR = 99; 18 | } 19 | 20 | message Volume { 21 | int32 account = 1; 22 | string id = 2; 23 | VolumeStatus status = 3; 24 | map metadata = 4; 25 | string created_at = 5; 26 | string updated_at = 6; 27 | 28 | int64 size = 7; 29 | string store = 8; 30 | string availability_area = 9; 31 | } 32 | 33 | message Snapshot { 34 | string id = 1; 35 | string volume = 2; 36 | VolumeStatus status = 3; 37 | map metadata = 4; 38 | string created_at = 5; 39 | string updated_at = 6; 40 | 41 | int64 size = 7; 42 | string store = 8; 43 | string availability_area = 9; 44 | } 45 | 46 | message PeerNegotiate { 47 | string IAm = 1; 48 | string YouAre = 2; 49 | } 50 | 51 | message RaftRPC { 52 | string volume = 1; 53 | int32 type = 2; 54 | bytes command = 3; 55 | string error = 4; 56 | } 57 | 58 | message PeerPing { 59 | int64 ts = 1; 60 | } 61 | 62 | message BlockCommandRequest { 63 | enum Cmd { 64 | WRITE = 0; 65 | READ = 1; 66 | ZERO = 2; 67 | } 68 | 69 | string volume = 1; 70 | Cmd cmd = 2; 71 | int64 offset = 3; 72 | int32 length = 4; 73 | bytes data = 5; 74 | } 75 | 76 | message BlockCommandResponse { 77 | string volume = 1; 78 | string retryAt = 2; //peer ID to retry to request at - i.e. peer not leader 79 | bytes data = 3; 80 | string error = 4; 81 | } 82 | 83 | message RPCMetadata { 84 | bool leaderOnly = 1; //to prevent stale reads if no caching on client side - all writes must still go to leader 85 | bool allowForwarding = 2; 86 | int32 TTL = 3; 87 | } 88 | 89 | message PeerRPC { 90 | RPCMetadata metadata = 1; 91 | oneof RPC { 92 | RaftRPC RaftRPC = 2; 93 | PeerPing PeerPing = 3; 94 | BlockCommandRequest BlockCommand = 4; 95 | BlockCommandResponse BlockCommandResponse = 5; 96 | } 97 | reserved 6 to 100; //future RPCs 98 | bool async = 101; 99 | } -------------------------------------------------------------------------------- /api/proto/v1/vpc.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | package vpc; 4 | option go_package = ".;vpc"; 5 | 6 | message VPC { 7 | int32 id = 1; 8 | int32 account = 2; 9 | string cidr = 3; 10 | map metadata = 4; 11 | int32 asn = 5; 12 | int32 vni = 6; 13 | } 14 | 15 | message Subnet { 16 | string id = 1; 17 | int32 vpc_id = 2; 18 | string region = 3; 19 | string cidr = 4; 20 | map metadata = 5; 21 | int32 inner_vlan = 6; 22 | } 23 | 24 | message InternetGW { 25 | string id = 1; 26 | int32 vpc_id = 2; 27 | } 28 | 29 | message PublicIP { 30 | string id = 1; 31 | int32 vpc_id = 2; 32 | string ip = 3; 33 | bool reserved = 4; 34 | } 35 | 36 | message FWRule { 37 | string proto = 2; 38 | string cidr = 3; 39 | int32 min_port = 4; 40 | int32 max_port = 5; 41 | } 42 | 43 | message FWRuleSet { 44 | string id = 1; 45 | string name = 2; 46 | repeated FWRule rules = 3; 47 | 48 | enum Direction { 49 | INBOUND = 0; 50 | OUTBOUND = 1; 51 | } 52 | 53 | Direction direction = 4; 54 | } 55 | 56 | message VPCsRequest {} 57 | message VPCsResponse { 58 | repeated VPC VPCs = 1; 59 | } 60 | 61 | message VPCInfoRequest { 62 | int32 vpc_id = 1; 63 | } 64 | 65 | message VPCInfoResponse { 66 | VPC vpc = 1; 67 | repeated Subnet subnets = 2; 68 | InternetGW internetGW = 3; 69 | } 70 | 71 | message SubnetsResponse { 72 | repeated Subnet subnets = 1; 73 | } 74 | 75 | message InternetGWsRespones { 76 | repeated InternetGW internet_gws = 1; 77 | } 78 | 79 | service VPCService { 80 | rpc VPCs(VPCsRequest) returns (VPCsResponse) {}; 81 | rpc VPCInfo(VPCInfoRequest) returns (VPCInfoResponse) {}; 82 | rpc Subnets(VPCInfoRequest) returns (SubnetsResponse) {}; 83 | rpc InternetGWs(VPCInfoRequest) returns (InternetGWsRespones) {}; 84 | } -------------------------------------------------------------------------------- /api/swagger/v1/l2.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "l2.proto", 5 | "version": "version not set" 6 | }, 7 | "schemes": [ 8 | "http", 9 | "https" 10 | ], 11 | "consumes": [ 12 | "application/json" 13 | ], 14 | "produces": [ 15 | "application/json" 16 | ], 17 | "paths": {}, 18 | "definitions": { 19 | "l2Empty": { 20 | "type": "object" 21 | }, 22 | "l2LinkStatus": { 23 | "type": "string", 24 | "enum": [ 25 | "DOWN", 26 | "UP", 27 | "MISSING" 28 | ], 29 | "default": "DOWN" 30 | }, 31 | "l2Nic": { 32 | "type": "object", 33 | "properties": { 34 | "vpc_id": { 35 | "type": "integer", 36 | "format": "int32" 37 | }, 38 | "hwaddr": { 39 | "type": "string" 40 | }, 41 | "id": { 42 | "type": "string" 43 | }, 44 | "name": { 45 | "type": "string" 46 | }, 47 | "index": { 48 | "type": "integer", 49 | "format": "int32" 50 | }, 51 | "vlan": { 52 | "type": "integer", 53 | "format": "int64" 54 | }, 55 | "ip": { 56 | "type": "array", 57 | "items": { 58 | "type": "string" 59 | } 60 | } 61 | } 62 | }, 63 | "l2NicStatusResponse": { 64 | "type": "object", 65 | "properties": { 66 | "status": { 67 | "$ref": "#/definitions/l2LinkStatus" 68 | } 69 | } 70 | }, 71 | "l2Stack": { 72 | "type": "object", 73 | "properties": { 74 | "vpc_id": { 75 | "type": "integer", 76 | "format": "int32" 77 | }, 78 | "bridge_link_name": { 79 | "type": "string" 80 | }, 81 | "bridge_link_index": { 82 | "type": "integer", 83 | "format": "int32" 84 | } 85 | } 86 | }, 87 | "l2StackChange": { 88 | "type": "object", 89 | "properties": { 90 | "vpc_id": { 91 | "type": "integer", 92 | "format": "int32" 93 | }, 94 | "action": { 95 | "type": "string" 96 | }, 97 | "status": { 98 | "$ref": "#/definitions/l2StackStatusResponse" 99 | } 100 | } 101 | }, 102 | "l2StackResponse": { 103 | "type": "object", 104 | "properties": { 105 | "stack": { 106 | "$ref": "#/definitions/l2Stack" 107 | }, 108 | "status": { 109 | "$ref": "#/definitions/l2StackStatusResponse" 110 | } 111 | } 112 | }, 113 | "l2StackStatusResponse": { 114 | "type": "object", 115 | "properties": { 116 | "bridge": { 117 | "$ref": "#/definitions/l2LinkStatus" 118 | }, 119 | "transport": { 120 | "$ref": "#/definitions/l2LinkStatus" 121 | } 122 | } 123 | }, 124 | "protobufAny": { 125 | "type": "object", 126 | "properties": { 127 | "type_url": { 128 | "type": "string" 129 | }, 130 | "value": { 131 | "type": "string", 132 | "format": "byte" 133 | } 134 | } 135 | }, 136 | "runtimeStreamError": { 137 | "type": "object", 138 | "properties": { 139 | "grpc_code": { 140 | "type": "integer", 141 | "format": "int32" 142 | }, 143 | "http_code": { 144 | "type": "integer", 145 | "format": "int32" 146 | }, 147 | "message": { 148 | "type": "string" 149 | }, 150 | "http_status": { 151 | "type": "string" 152 | }, 153 | "details": { 154 | "type": "array", 155 | "items": { 156 | "$ref": "#/definitions/protobufAny" 157 | } 158 | } 159 | } 160 | } 161 | }, 162 | "x-stream-definitions": { 163 | "l2StackChange": { 164 | "type": "object", 165 | "properties": { 166 | "result": { 167 | "$ref": "#/definitions/l2StackChange" 168 | }, 169 | "error": { 170 | "$ref": "#/definitions/runtimeStreamError" 171 | } 172 | }, 173 | "title": "Stream result of l2StackChange" 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /api/swagger/v1/l2Controller.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "l2Controller.proto", 5 | "version": "version not set" 6 | }, 7 | "schemes": [ 8 | "http", 9 | "https" 10 | ], 11 | "consumes": [ 12 | "application/json" 13 | ], 14 | "produces": [ 15 | "application/json" 16 | ], 17 | "paths": {}, 18 | "definitions": { 19 | "l2ControllerBroadcastEndpointResponse": { 20 | "type": "object", 21 | "properties": { 22 | "IP": { 23 | "type": "array", 24 | "items": { 25 | "type": "string" 26 | } 27 | } 28 | } 29 | }, 30 | "l2ControllerLookupResponse": { 31 | "type": "object", 32 | "properties": { 33 | "MAC": { 34 | "type": "string" 35 | }, 36 | "IP": { 37 | "type": "string" 38 | } 39 | } 40 | }, 41 | "l2ControllerLookupType": { 42 | "type": "string", 43 | "enum": [ 44 | "MAC", 45 | "IP" 46 | ], 47 | "default": "MAC" 48 | }, 49 | "l2ControllerMACIPResp": { 50 | "type": "object" 51 | }, 52 | "l2ControllerRegResponse": { 53 | "type": "object" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/swagger/v1/machinery.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "machinery.proto", 5 | "version": "version not set" 6 | }, 7 | "schemes": [ 8 | "http", 9 | "https" 10 | ], 11 | "consumes": [ 12 | "application/json" 13 | ], 14 | "produces": [ 15 | "application/json" 16 | ], 17 | "paths": {}, 18 | "definitions": { 19 | "MachineCapability": { 20 | "type": "string", 21 | "enum": [ 22 | "ORCHEST", 23 | "COMPUTE", 24 | "NETWORK", 25 | "STORAGE" 26 | ], 27 | "default": "ORCHEST" 28 | }, 29 | "machineryDeviceType": { 30 | "type": "string", 31 | "enum": [ 32 | "ROUTER", 33 | "VM", 34 | "BLOCK" 35 | ], 36 | "default": "ROUTER" 37 | }, 38 | "machineryMachine": { 39 | "type": "object", 40 | "properties": { 41 | "id": { 42 | "type": "string" 43 | }, 44 | "capabilities": { 45 | "type": "array", 46 | "items": { 47 | "$ref": "#/definitions/MachineCapability" 48 | } 49 | }, 50 | "last_checkin": { 51 | "type": "string" 52 | }, 53 | "availability_zone": { 54 | "type": "string" 55 | }, 56 | "versions": { 57 | "type": "object", 58 | "additionalProperties": { 59 | "type": "string" 60 | } 61 | }, 62 | "limits": { 63 | "type": "object", 64 | "additionalProperties": { 65 | "type": "integer", 66 | "format": "int32" 67 | } 68 | } 69 | } 70 | }, 71 | "machineryMachinesResponse": { 72 | "type": "object", 73 | "properties": { 74 | "machines": { 75 | "type": "array", 76 | "items": { 77 | "$ref": "#/definitions/machineryMachine" 78 | } 79 | } 80 | } 81 | }, 82 | "machineryPingResponse": { 83 | "type": "object" 84 | }, 85 | "machineryPlaceResponse": { 86 | "type": "object" 87 | }, 88 | "machineryPlacement": { 89 | "type": "object", 90 | "properties": { 91 | "id": { 92 | "type": "string" 93 | }, 94 | "machine_id": { 95 | "type": "string" 96 | }, 97 | "device_type": { 98 | "$ref": "#/definitions/machineryDeviceType" 99 | }, 100 | "device_id": { 101 | "type": "string" 102 | } 103 | } 104 | }, 105 | "machineryPlacementAction": { 106 | "type": "string", 107 | "enum": [ 108 | "ASSIGN", 109 | "REPLACE", 110 | "UNASSIGN" 111 | ], 112 | "default": "ASSIGN" 113 | }, 114 | "machineryPlacementsResponse": { 115 | "type": "object", 116 | "properties": { 117 | "placements": { 118 | "type": "array", 119 | "items": { 120 | "$ref": "#/definitions/machineryPlacement" 121 | } 122 | }, 123 | "types": { 124 | "type": "array", 125 | "items": { 126 | "$ref": "#/definitions/machineryDeviceType" 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /api/swagger/v1/volumes.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "volumes.proto", 5 | "version": "version not set" 6 | }, 7 | "schemes": [ 8 | "http", 9 | "https" 10 | ], 11 | "consumes": [ 12 | "application/json" 13 | ], 14 | "produces": [ 15 | "application/json" 16 | ], 17 | "paths": {}, 18 | "definitions": {} 19 | } 20 | -------------------------------------------------------------------------------- /api/swagger/v1/vpc.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "vpc.proto", 5 | "version": "version not set" 6 | }, 7 | "schemes": [ 8 | "http", 9 | "https" 10 | ], 11 | "consumes": [ 12 | "application/json" 13 | ], 14 | "produces": [ 15 | "application/json" 16 | ], 17 | "paths": {}, 18 | "definitions": { 19 | "vpcInternetGW": { 20 | "type": "object", 21 | "properties": { 22 | "id": { 23 | "type": "string" 24 | }, 25 | "vpc_id": { 26 | "type": "integer", 27 | "format": "int32" 28 | } 29 | } 30 | }, 31 | "vpcInternetGWsRespones": { 32 | "type": "object", 33 | "properties": { 34 | "internet_gws": { 35 | "type": "array", 36 | "items": { 37 | "$ref": "#/definitions/vpcInternetGW" 38 | } 39 | } 40 | } 41 | }, 42 | "vpcSubnet": { 43 | "type": "object", 44 | "properties": { 45 | "id": { 46 | "type": "string" 47 | }, 48 | "vpc_id": { 49 | "type": "integer", 50 | "format": "int32" 51 | }, 52 | "region": { 53 | "type": "string" 54 | }, 55 | "cidr": { 56 | "type": "string" 57 | }, 58 | "metadata": { 59 | "type": "object", 60 | "additionalProperties": { 61 | "type": "string" 62 | } 63 | }, 64 | "inner_vlan": { 65 | "type": "integer", 66 | "format": "int32" 67 | } 68 | } 69 | }, 70 | "vpcSubnetsResponse": { 71 | "type": "object", 72 | "properties": { 73 | "subnets": { 74 | "type": "array", 75 | "items": { 76 | "$ref": "#/definitions/vpcSubnet" 77 | } 78 | } 79 | } 80 | }, 81 | "vpcVPC": { 82 | "type": "object", 83 | "properties": { 84 | "id": { 85 | "type": "integer", 86 | "format": "int32" 87 | }, 88 | "account": { 89 | "type": "integer", 90 | "format": "int32" 91 | }, 92 | "cidr": { 93 | "type": "string" 94 | }, 95 | "metadata": { 96 | "type": "object", 97 | "additionalProperties": { 98 | "type": "string" 99 | } 100 | }, 101 | "asn": { 102 | "type": "integer", 103 | "format": "int32" 104 | }, 105 | "vni": { 106 | "type": "integer", 107 | "format": "int32" 108 | } 109 | } 110 | }, 111 | "vpcVPCInfoResponse": { 112 | "type": "object", 113 | "properties": { 114 | "vpc": { 115 | "$ref": "#/definitions/vpcVPC" 116 | }, 117 | "subnets": { 118 | "type": "array", 119 | "items": { 120 | "$ref": "#/definitions/vpcSubnet" 121 | } 122 | }, 123 | "internetGW": { 124 | "$ref": "#/definitions/vpcInternetGW" 125 | } 126 | } 127 | }, 128 | "vpcVPCsResponse": { 129 | "type": "object", 130 | "properties": { 131 | "VPCs": { 132 | "type": "array", 133 | "items": { 134 | "$ref": "#/definitions/vpcVPC" 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /cmd/hyper/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/tcfw/vpc/pkg/hyper/cmd" 8 | ) 9 | 10 | func main() { 11 | cmd := cmd.NewDefaultCommand() 12 | 13 | if err := cmd.Execute(); err != nil { 14 | fmt.Fprintf(os.Stderr, "%v\n", err) 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/l2agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/tcfw/vpc/pkg/l2/cmd" 8 | ) 9 | 10 | func main() { 11 | cmd := cmd.NewDefaultCommand() 12 | 13 | if err := cmd.Execute(); err != nil { 14 | fmt.Fprintf(os.Stderr, "%v\n", err) 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/l3agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/tcfw/vpc/pkg/l3/cmd" 8 | ) 9 | 10 | func main() { 11 | dcmd := cmd.NewDefaultCommand() 12 | 13 | if err := dcmd.Execute(); err != nil { 14 | fmt.Fprintf(os.Stderr, "%v\n", err) 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/sbs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/tcfw/vpc/pkg/sbs/cmd" 8 | ) 9 | 10 | func main() { 11 | cmd := cmd.NewDefaultCommand() 12 | 13 | if err := cmd.Execute(); err != nil { 14 | fmt.Fprintf(os.Stderr, "%v\n", err) 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/vpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/tcfw/vpc/pkg/vpc/cmd" 8 | ) 9 | 10 | func main() { 11 | cmd := cmd.NewDefaultCommand() 12 | 13 | if err := cmd.Execute(); err != nil { 14 | fmt.Fprintf(os.Stderr, "%v\n", err) 15 | os.Exit(1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/res/vpc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcfw/vpc/a9890c3e7311f7e6ac464bf83db1ea83caa4c2fb/docs/res/vpc.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tcfw/vpc 2 | 3 | go 1.13 4 | 5 | require ( 6 | aqwari.net/xml v0.0.0-20200619145941-6c62842e69c1 7 | github.com/DataDog/zstd v1.4.5 // indirect 8 | github.com/apparentlymart/go-cidr v1.0.1 9 | github.com/cilium/ebpf v0.0.0-20200617135954-7acf5cc039f4 10 | github.com/coreos/go-iptables v0.4.5 11 | github.com/denisbrodbeck/machineid v1.0.1 12 | github.com/dgraph-io/badger/v2 v2.0.3 13 | github.com/dgraph-io/ristretto v0.0.3 // indirect 14 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect 15 | github.com/diskfs/go-diskfs v1.1.1 16 | github.com/eapache/queue v1.1.0 // indirect 17 | github.com/esiqveland/gogfapi v0.0.0-20200421115158-22e95f628744 18 | github.com/golang/mock v1.4.3 // indirect 19 | github.com/golang/protobuf v1.4.2 20 | github.com/google/gopacket v1.1.17 21 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect 22 | github.com/hashicorp/raft v1.1.2 23 | github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 // indirect 24 | github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8 25 | github.com/juliengk/go-netfilter v0.0.0-20190717112259-89f054e40987 26 | github.com/k-sone/critbitgo v1.4.0 // indirect 27 | github.com/kpango/fastime v1.0.16 28 | github.com/kr/text v0.2.0 // indirect 29 | github.com/lib/pq v1.7.0 30 | github.com/lorenzosaino/go-sysctl v0.1.1 31 | github.com/lucas-clemente/quic-go v0.17.3 32 | github.com/marten-seemann/qtls v0.10.0 // indirect 33 | github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 // indirect 34 | github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 // indirect 35 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 36 | github.com/mitchellh/mapstructure v1.3.2 // indirect 37 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 38 | github.com/onsi/ginkgo v1.13.0 // indirect 39 | github.com/osrg/gobgp v0.0.0-20200602125502-33e3bb6b2756 40 | github.com/pelletier/go-toml v1.8.0 // indirect 41 | github.com/pkg/errors v0.9.1 // indirect 42 | github.com/sirupsen/logrus v1.6.0 43 | github.com/smartystreets/assertions v1.1.1 // indirect 44 | github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 45 | github.com/spf13/afero v1.3.1 // indirect 46 | github.com/spf13/cast v1.3.1 // indirect 47 | github.com/spf13/cobra v1.0.0 48 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 49 | github.com/spf13/pflag v1.0.5 // indirect 50 | github.com/spf13/viper v1.7.0 51 | github.com/src-d/envconfig v1.0.0 // indirect 52 | github.com/stretchr/testify v1.6.1 53 | github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e 54 | github.com/u-root/u-root v6.0.0+incompatible // indirect 55 | github.com/vishvananda/netlink v1.1.0 56 | github.com/vishvananda/netns v0.0.0-20200520041808-52d707b772fe 57 | github.com/vmihailenco/msgpack v4.0.4+incompatible 58 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect 59 | golang.org/x/mod v0.3.0 // indirect 60 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect 61 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 62 | golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 63 | golang.org/x/text v0.3.3 // indirect 64 | golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f // indirect 65 | google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5 // indirect 66 | google.golang.org/grpc v1.30.0 67 | google.golang.org/protobuf v1.25.0 68 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 69 | gopkg.in/ini.v1 v1.57.0 // indirect 70 | gopkg.in/src-d/go-log.v1 v1.0.2 71 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect 72 | libvirt.org/libvirt-go v6.4.0+incompatible 73 | ) 74 | -------------------------------------------------------------------------------- /pkg/hyper/cloudinit.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | 8 | diskfs "github.com/diskfs/go-diskfs" 9 | "github.com/diskfs/go-diskfs/disk" 10 | "github.com/diskfs/go-diskfs/filesystem" 11 | "github.com/diskfs/go-diskfs/filesystem/iso9660" 12 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 13 | ) 14 | 15 | const ( 16 | cloudName = "vpc" 17 | diskStore = "/var/local/vpc/cidisks" 18 | ) 19 | 20 | //InstanceMetadata basic instance info 21 | type InstanceMetadata struct { 22 | InstanceID string `json:"instance-id"` 23 | CloudName string `json:"cloud-name"` 24 | } 25 | 26 | func createCloudInitISO(vm *hyperAPI.VM) (*os.File, error) { 27 | instanceMD := &InstanceMetadata{ 28 | InstanceID: vm.Id, 29 | CloudName: cloudName, 30 | } 31 | 32 | diskImg, err := ciDataDisk(vm) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | mdbytes, err := json.Marshal(instanceMD) 38 | if err != nil { 39 | defer os.Remove(diskImg.Name()) 40 | return diskImg, err 41 | } 42 | 43 | size := int64(len(vm.UserData) + len(mdbytes) + 1024) 44 | if size < 32768 { 45 | //min ISO file size 46 | size = 32768 + 2048 + 2048 + 2048 47 | } 48 | 49 | diskImg.Truncate(size) 50 | 51 | ciDisk, err := diskfs.OpenWithMode(diskImg.Name(), diskfs.ReadWriteExclusive) 52 | if err != nil { 53 | defer os.Remove(diskImg.Name()) 54 | return diskImg, err 55 | } 56 | 57 | ciDisk.LogicalBlocksize = 2048 58 | fspec := disk.FilesystemSpec{Partition: 0, FSType: filesystem.TypeISO9660, VolumeLabel: "cidata"} 59 | 60 | fs, err := ciDisk.CreateFilesystem(fspec) 61 | if err != nil { 62 | defer os.Remove(diskImg.Name()) 63 | return diskImg, err 64 | } 65 | 66 | //Metadata file 67 | fmd, err := fs.OpenFile("meta-data", os.O_CREATE|os.O_RDWR) 68 | if err != nil { 69 | defer os.Remove(diskImg.Name()) 70 | return diskImg, fmt.Errorf("failed to open md: %s", err) 71 | } 72 | if _, err := fmd.Write(mdbytes); err != nil { 73 | defer os.Remove(diskImg.Name()) 74 | return nil, fmt.Errorf("failed to write md: %s", err) 75 | } 76 | 77 | //User-data 78 | if vm.UserData != nil { 79 | fud, err := fs.OpenFile("user-data", os.O_CREATE|os.O_RDWR) 80 | if err != nil { 81 | defer os.Remove(diskImg.Name()) 82 | return nil, fmt.Errorf("failed to write ud: %s", err) 83 | } 84 | 85 | if _, err := fud.Write(vm.UserData); err != nil { 86 | defer os.Remove(diskImg.Name()) 87 | return nil, err 88 | } 89 | } 90 | 91 | //Finalise 92 | iso, ok := fs.(*iso9660.FileSystem) 93 | isows := iso.Workspace() 94 | if !ok { 95 | defer os.Remove(diskImg.Name()) 96 | return nil, fmt.Errorf("not an iso9660 filesystem") 97 | } 98 | if err = iso.Finalize(iso9660.FinalizeOptions{}); err != nil { 99 | defer os.Remove(diskImg.Name()) 100 | return nil, err 101 | } 102 | 103 | //Remove the temp workspace since diskfs does not 104 | os.RemoveAll(isows) 105 | 106 | return diskImg, diskImg.Sync() 107 | 108 | } 109 | 110 | func ciDataDisk(vm *hyperAPI.VM) (*os.File, error) { 111 | if _, err := os.Stat(diskStore); os.IsNotExist(err) { 112 | err := os.MkdirAll(diskStore, os.ModePerm) 113 | if err != nil { 114 | return nil, fmt.Errorf("failed to create local disk store: %s", err) 115 | } 116 | } 117 | 118 | return os.OpenFile(ciDiskName(vm), os.O_RDWR|os.O_CREATE, os.ModePerm) 119 | } 120 | 121 | func ciDiskName(vm *hyperAPI.VM) string { 122 | return fmt.Sprintf("%s/ci.%s.iso", diskStore, vm.Id) 123 | } 124 | -------------------------------------------------------------------------------- /pkg/hyper/cloudinit_test.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 10 | ) 11 | 12 | func TestCreateCloudInitISO(t *testing.T) { 13 | vm := &hyperAPI.VM{ 14 | Id: "abcdef-test", 15 | UserData: []byte(` 16 | #cloud-init 17 | password: test 18 | chpasswd: { expire: False } 19 | ssh_pwauth: True 20 | `), 21 | } 22 | 23 | isoFile, err := createCloudInitISO(vm) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | defer func() { 29 | isoFile.Close() 30 | os.Remove(isoFile.Name()) 31 | }() 32 | 33 | t.Logf("ISO filename: %s", isoFile.Name()) 34 | 35 | //TODO(tcfw): find some way to validate ISO file properly 36 | 37 | scanner := bufio.NewScanner(isoFile) 38 | loc := false 39 | for scanner.Scan() { 40 | if strings.Contains(scanner.Text(), "#cloud-init") { 41 | loc = true 42 | break 43 | } 44 | } 45 | 46 | if loc == false { 47 | t.Fatal("failed to find cloud-init user-data header") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/hyper/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | // NewDefaultCommand creates the `hyper` command and its nested children. 8 | func NewDefaultCommand() *cobra.Command { 9 | // Parent command to which all subcommands are added. 10 | cmds := &cobra.Command{ 11 | Use: "hyper", 12 | Short: "hyper", 13 | Long: `hyper providers hypervised compute resources`, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | cmd.Help() 16 | }, 17 | } 18 | 19 | cmds.AddCommand(newListCmd()) 20 | 21 | return cmds 22 | } 23 | -------------------------------------------------------------------------------- /pkg/hyper/cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/spf13/cobra" 8 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 9 | "github.com/tcfw/vpc/pkg/hyper" 10 | ) 11 | 12 | //newListCmd provides a command to delete vpcs 13 | func newListCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "list", 16 | Short: "lists local vms", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | l, err := hyper.NewLocalLibVirtConn() 19 | if err != nil { 20 | log.Fatalf("failed: %s", err) 21 | } 22 | 23 | defer l.Close() 24 | 25 | if err := hyper.ApplyDesiredState(l, "7897fad0-e6ff-44df-9e3c-cb6c939f8bb6", hyperAPI.PowerState_SHUTDOWN); err != nil { 26 | log.Fatalf("%s", err) 27 | } 28 | 29 | f, err := hyper.AvailableDisks() 30 | if err != nil { 31 | log.Fatalf("%s", err) 32 | } 33 | fmt.Printf("%+v", f) 34 | 35 | // d, _ := l.LookupDomainByUUIDString("7897fad0-e6ff-44df-9e3c-cb6c939f8bb6") 36 | // if err := hyper.Snapshot(d); err != nil { 37 | // fmt.Printf("ERR: %s", err) 38 | // } 39 | }, 40 | } 41 | 42 | return cmd 43 | } 44 | -------------------------------------------------------------------------------- /pkg/hyper/disks.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | 7 | "github.com/esiqveland/gogfapi/gfapi" 8 | 9 | libvirt "libvirt.org/libvirt-go" 10 | ) 11 | 12 | //AvailableDisks lists available disks 13 | func AvailableDisks() ([]string, error) { 14 | files := []string{} 15 | 16 | vol := &gfapi.Volume{} 17 | if err := vol.Init("disks", "localhost"); err != nil { 18 | return nil, fmt.Errorf("failed to connect: %s", err) 19 | } 20 | 21 | if err := vol.Mount(); err != nil { 22 | return nil, fmt.Errorf("failed to mount: %s", err) 23 | } 24 | defer vol.Unmount() 25 | 26 | f, err := vol.Open("/") 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | volFiles, err := f.Readdir(0) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | for _, f := range volFiles { 37 | name := f.Name() 38 | if name == "." || name == ".." { 39 | continue 40 | } 41 | 42 | files = append(files, name) 43 | } 44 | 45 | return files, nil 46 | } 47 | 48 | //Snapshot attempts to create a snapshot of a running VM 49 | func Snapshot(d *libvirt.Domain) error { 50 | desc := &DomainBackup{ 51 | Disks: []DomainBackupDisk{ 52 | { 53 | Name: "vda", 54 | Type: "file", 55 | Target: DomainBackupDiskTarget{ 56 | File: "/home/tom/Desktop/backup.backup", 57 | }, 58 | Driver: DomainBackupDiskDriver{ 59 | Type: "raw", 60 | }, 61 | }, 62 | }, 63 | } 64 | 65 | checkpoint := &DomainCheckpoint{ 66 | Description: "test", 67 | Disks: []DomainCheckpointDisk{ 68 | { 69 | Name: "vda", 70 | }, 71 | }, 72 | } 73 | 74 | descBytes, _ := xml.Marshal(desc) 75 | checkpointBytes, _ := xml.Marshal(checkpoint) 76 | 77 | fmt.Printf("BU: %s\nCP: %s", string(descBytes), string(checkpointBytes)) 78 | 79 | return d.BackupBegin(string(descBytes), string(checkpointBytes), libvirt.DOMAIN_BACKUP_BEGIN_REUSE_EXTERNAL) 80 | } 81 | -------------------------------------------------------------------------------- /pkg/hyper/domain.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/xml" 6 | "fmt" 7 | "os" 8 | 9 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 10 | "libvirt.org/libvirt-go" 11 | ) 12 | 13 | //DefineVM takes in an API desc and creates a hypervisor accepted VM 14 | func DefineVM(c *libvirt.Connect, vm *hyperAPI.VM) error { 15 | if vm.Id == "" { 16 | return fmt.Errorf("vm has no id") 17 | } 18 | 19 | if vm.HyperId != "" { 20 | d, err := c.LookupDomainByUUIDString(vm.HyperId) 21 | if err != nil { 22 | return fmt.Errorf("failed to check for existing: %s", err) 23 | } 24 | if d != nil { 25 | return fmt.Errorf("vm already defined") 26 | } 27 | } 28 | 29 | //construct cloud-init disk 30 | var cidata *os.File 31 | cidiskn := ciDiskName(vm) 32 | if _, err := os.Stat(cidiskn); os.IsExist(err) { 33 | cidata, err = os.OpenFile(cidiskn, os.O_EXCL|os.O_RDWR, os.ModePerm) 34 | if err != nil { 35 | return fmt.Errorf("failed to open cidata: %s", err) 36 | } 37 | } else { 38 | cidata, err = createCloudInitISO(vm) 39 | if err != nil { 40 | return fmt.Errorf("failed to create cidata: %s", err) 41 | } 42 | } 43 | 44 | //Closed for IO 45 | cidata.Close() 46 | 47 | desc, err := GetTemplate(vm) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | desc.Devices.Disks = append(desc.Devices.Disks, DomainDisk{ 53 | Type: "file", 54 | Device: "disk", 55 | ReadOnly: &DomainDiskReadOnly{}, 56 | Target: DomainDiskTarget{Bus: "virtio", Dev: "vdz"}, 57 | Driver: DomainDiskDriver{Name: "qemu", Type: "raw"}, 58 | Source: DomainDiskSource{File: cidata.Name()}, 59 | Address: DomainDeviceAddr{ //TODO(tcfw) check if PCI slot is available 60 | Type: "pci", 61 | Domain: "0x0000", 62 | Bus: "0x09", 63 | Slot: "0x00", 64 | Function: "0x00", 65 | }, 66 | IOTune: &DomainDiskIOTune{ 67 | TotalIopsSec: 100, 68 | TotalIopsSecMax: 200, 69 | TotalIopsSecMaxLength: 10, 70 | }, 71 | }) 72 | 73 | uuid, err := createDomain(c, desc) 74 | if err != nil { 75 | return fmt.Errorf("failed to define vm: %s", err) 76 | } 77 | vm.HyperId = hex.EncodeToString(uuid) 78 | return nil 79 | 80 | } 81 | 82 | func createDomain(c *libvirt.Connect, desc interface{}) ([]byte, error) { 83 | switch desc.(type) { 84 | case *DomainDesc: 85 | descBytes, err := xml.Marshal(desc) 86 | if err != nil { 87 | return nil, fmt.Errorf("failed to marshal to XML desc: %s", err) 88 | } 89 | return createXMLDomain(c, string(descBytes)) 90 | case []byte: 91 | return createXMLDomain(c, string(desc.([]byte))) 92 | case string: 93 | return createXMLDomain(c, desc.(string)) 94 | } 95 | 96 | return nil, fmt.Errorf("unknown desc format") 97 | } 98 | 99 | func createXMLDomain(c *libvirt.Connect, descXML string) ([]byte, error) { 100 | d, err := c.DomainDefineXML(descXML) 101 | if err != nil { 102 | return nil, fmt.Errorf("failed to define domain: %s", err) 103 | } 104 | 105 | uuid, err := d.GetUUID() 106 | if err != nil { 107 | return nil, fmt.Errorf("failed to retrieve uuid from defined domain: %s", err) 108 | } 109 | 110 | return uuid, nil 111 | } 112 | -------------------------------------------------------------------------------- /pkg/hyper/domain_test.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 9 | ) 10 | 11 | func TestDefineVM(t *testing.T) { 12 | vm := &hyperAPI.VM{ 13 | Id: "test-1", 14 | TemplateId: ` 15 | 4194304 16 | 4194304 17 | 2 18 | 19 | hvm 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | destroy 34 | restart 35 | restart 36 | 37 | 38 | 39 | 40 | 41 | /usr/bin/qemu-system-x86_64 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 | 51 |
52 | 53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 |
71 | 72 | 73 | 74 | 75 |
76 | 77 | 78 | 79 | 80 |
81 | 82 | 83 | 84 | 85 |
86 | 87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | 101 | 102 | /dev/urandom 103 |
104 | 105 | 106 | `, 107 | } 108 | 109 | c, err := testLibVirtConn() 110 | if err != nil { 111 | log.Fatalf("failed: %s", err) 112 | } 113 | 114 | defer c.Close() 115 | 116 | err = DefineVM(c, vm) 117 | if err != nil { 118 | log.Fatal(err) 119 | } 120 | 121 | assert.NotEmpty(t, vm.HyperId) 122 | } 123 | -------------------------------------------------------------------------------- /pkg/hyper/hyper.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 9 | libvirt "libvirt.org/libvirt-go" 10 | ) 11 | 12 | //List provides a list of running VMs 13 | func List(c *libvirt.Connect) { 14 | domains, err := c.ListAllDomains(libvirt.CONNECT_LIST_DOMAINS_ACTIVE | libvirt.CONNECT_LIST_DOMAINS_INACTIVE) 15 | if err != nil { 16 | log.Fatalf("failed to retrieve domains: %v", err) 17 | } 18 | 19 | fmt.Println("ID\tName\t\tUUID\tState") 20 | fmt.Printf("--------------------------------------------------------\n") 21 | for _, d := range domains { 22 | state, _, err := d.GetState() 23 | if err != nil { 24 | log.Fatalln(err) 25 | } 26 | id, _ := d.GetID() 27 | name, _ := d.GetName() 28 | uuid, _ := d.GetUUID() 29 | fmt.Printf("%d\t%s\t%x\t%v\n", id, name, uuid, state) 30 | } 31 | } 32 | 33 | //ApplyDesiredState takes in the desired state of the VM and attempts to apply the desired state 34 | func ApplyDesiredState(c *libvirt.Connect, uuid string, desiredState hyperAPI.PowerState) error { 35 | d, err := c.LookupDomainByUUIDString(uuid) 36 | if err != nil { 37 | return fmt.Errorf("failed to lookup: %s", err) 38 | } 39 | 40 | current, err := setDesirePower(d, desiredState) 41 | 42 | fmt.Printf("%+v", current) 43 | 44 | return err 45 | } 46 | 47 | //Stats gets a current running VM's io/resource stats 48 | func Stats(c *libvirt.Connect, uuid string) error { 49 | //Check exists 50 | d, err := c.LookupDomainByUUIDString(uuid) 51 | if err != nil { 52 | return fmt.Errorf("failed to lookup: %s", err) 53 | } 54 | 55 | //Check is running 56 | s, _, err := d.GetState() 57 | if err != nil { 58 | return fmt.Errorf("failed check state: %s", err) 59 | } 60 | if s != libvirt.DOMAIN_RUNNING { 61 | return fmt.Errorf("domain not running: %d", s) 62 | } 63 | 64 | memStats, err := memStats(d) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | fmt.Printf("STAT: %+v\n", memStats) 70 | fmt.Printf("STAT Used: %+v\n", memStats.Available-memStats.Unused) 71 | fmt.Printf("STAT Last: %s\n", time.Unix(int64(memStats.LastUpdate), 0)) 72 | 73 | cpuStats, total, err := cpuStats(d) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | fmt.Printf("%+v\n", cpuStats) 79 | fmt.Printf("Total: %+#v\n", total) 80 | 81 | netStats, err := netStats(d) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | fmt.Printf("NET: %+v\n", netStats) 87 | 88 | _, dTotal, err := diskStats(d) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | fmt.Printf("DISK: %+v\n", dTotal) 94 | 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /pkg/hyper/libvirt.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "fmt" 5 | 6 | libvirt "libvirt.org/libvirt-go" 7 | ) 8 | 9 | //NewLocalLibVirtConn connects to the local libvirt daemon 10 | func NewLocalLibVirtConn() (*libvirt.Connect, error) { 11 | l, err := libvirt.NewConnect("qemu:///system") 12 | if err != nil { 13 | return nil, fmt.Errorf("failed to connect: %v", err) 14 | } 15 | 16 | return l, nil 17 | } 18 | 19 | func testLibVirtConn() (*libvirt.Connect, error) { 20 | return libvirt.NewConnect("test:///default") 21 | } 22 | -------------------------------------------------------------------------------- /pkg/hyper/power.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "fmt" 5 | 6 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 7 | libvirt "libvirt.org/libvirt-go" 8 | ) 9 | 10 | func setDesirePower(d *libvirt.Domain, desiredState hyperAPI.PowerState) (hyperAPI.PowerState, error) { 11 | current, _, err := d.GetState() 12 | if err != nil { 13 | return hyperAPI.PowerState_NONE, err 14 | } 15 | 16 | switch current { 17 | case libvirt.DOMAIN_CRASHED: 18 | case libvirt.DOMAIN_BLOCKED: 19 | switch desiredState { 20 | case hyperAPI.PowerState_RUNNING: 21 | return appliedState(hyperAPI.PowerState_CRASHED, d.Reboot(libvirt.DOMAIN_REBOOT_DEFAULT)) 22 | case hyperAPI.PowerState_SHUTDOWN: 23 | return appliedState(hyperAPI.PowerState_SHUTOFF, d.Destroy()) 24 | } 25 | case libvirt.DOMAIN_RUNNING: 26 | switch desiredState { 27 | case hyperAPI.PowerState_SHUTDOWN: 28 | return appliedState(hyperAPI.PowerState_SHUTDOWN, d.ShutdownFlags(libvirt.DOMAIN_SHUTDOWN_ACPI_POWER_BTN)) 29 | case hyperAPI.PowerState_SHUTOFF: 30 | return appliedState(hyperAPI.PowerState_SHUTOFF, d.DestroyFlags(libvirt.DOMAIN_DESTROY_GRACEFUL)) 31 | } 32 | case libvirt.DOMAIN_PAUSED: 33 | switch desiredState { 34 | case hyperAPI.PowerState_SHUTDOWN: 35 | return appliedState(hyperAPI.PowerState_SHUTOFF, d.Shutdown()) 36 | case hyperAPI.PowerState_RUNNING: 37 | return appliedState(hyperAPI.PowerState_RUNNING, d.Resume()) 38 | } 39 | case libvirt.DOMAIN_SHUTDOWN: 40 | switch desiredState { 41 | case hyperAPI.PowerState_SHUTOFF: 42 | return appliedState(hyperAPI.PowerState_SHUTOFF, d.DestroyFlags(libvirt.DOMAIN_DESTROY_DEFAULT)) 43 | } 44 | case libvirt.DOMAIN_SHUTOFF: 45 | switch desiredState { 46 | case hyperAPI.PowerState_RUNNING: 47 | return appliedState(hyperAPI.PowerState_RUNNING, d.Create()) 48 | } 49 | } 50 | 51 | return libVirtToAPI(current), nil 52 | } 53 | 54 | func appliedState(endCurrent hyperAPI.PowerState, err error) (hyperAPI.PowerState, error) { 55 | if err != nil { 56 | return hyperAPI.PowerState_NONE, fmt.Errorf("failed to apply state: %s", err) 57 | } 58 | 59 | return endCurrent, nil 60 | } 61 | 62 | func libVirtToAPI(state libvirt.DomainState) hyperAPI.PowerState { 63 | switch state { 64 | case libvirt.DOMAIN_CRASHED: 65 | case libvirt.DOMAIN_BLOCKED: 66 | return hyperAPI.PowerState_CRASHED 67 | case libvirt.DOMAIN_RUNNING: 68 | return hyperAPI.PowerState_RUNNING 69 | case libvirt.DOMAIN_SHUTOFF: 70 | return hyperAPI.PowerState_SHUTOFF 71 | case libvirt.DOMAIN_SHUTDOWN: 72 | return hyperAPI.PowerState_SHUTDOWN 73 | case libvirt.DOMAIN_PAUSED: 74 | case libvirt.DOMAIN_PMSUSPENDED: 75 | return hyperAPI.PowerState_MIGRATING 76 | case libvirt.DOMAIN_NOSTATE: 77 | default: 78 | // 79 | } 80 | 81 | return hyperAPI.PowerState_NONE 82 | } 83 | -------------------------------------------------------------------------------- /pkg/hyper/stats.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "math" 7 | "time" 8 | 9 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 10 | libvirt "libvirt.org/libvirt-go" 11 | ) 12 | 13 | func memStats(d *libvirt.Domain) (*hyperAPI.MemStats, error) { 14 | mStats, err := d.MemoryStats(15, 0) 15 | if err != nil { 16 | return nil, fmt.Errorf("failed to get mem stats: %s", err) 17 | } 18 | 19 | stats := &hyperAPI.MemStats{} 20 | 21 | for _, stat := range mStats { 22 | switch libvirt.DomainMemoryStatTags(stat.Tag) { 23 | case libvirt.DOMAIN_MEMORY_STAT_SWAP_IN: 24 | stats.SwapIn = stat.Val 25 | break 26 | case libvirt.DOMAIN_MEMORY_STAT_SWAP_OUT: 27 | stats.SwapOut = stat.Val 28 | break 29 | case libvirt.DOMAIN_MEMORY_STAT_AVAILABLE: 30 | stats.Available = stat.Val 31 | break 32 | case libvirt.DOMAIN_MEMORY_STAT_UNUSED: 33 | stats.Unused = stat.Val 34 | break 35 | case libvirt.DOMAIN_MEMORY_STAT_MAJOR_FAULT: 36 | stats.MajorsFaults = stat.Val 37 | break 38 | case libvirt.DOMAIN_MEMORY_STAT_MINOR_FAULT: 39 | stats.MinorFaults = stat.Val 40 | break 41 | case libvirt.DOMAIN_MEMORY_STAT_USABLE: 42 | stats.Usable = stat.Val 43 | break 44 | case libvirt.DOMAIN_MEMORY_STAT_LAST_UPDATE: 45 | stats.LastUpdate = stat.Val 46 | break 47 | } 48 | } 49 | 50 | return stats, nil 51 | } 52 | 53 | func cpuStats(d *libvirt.Domain) ([]*hyperAPI.VCPUStats, *hyperAPI.VCPUStats, error) { 54 | stats := []*hyperAPI.VCPUStats{} 55 | total := &hyperAPI.VCPUStats{} 56 | 57 | tn := 1 * time.Second 58 | n, _ := d.GetMaxVcpus() 59 | 60 | p1, err := d.GetCPUStats(0, n, 0) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | time.Sleep(tn) 66 | 67 | p2, err := d.GetCPUStats(0, n, 0) 68 | if err != nil { 69 | return nil, nil, err 70 | } 71 | 72 | for i := uint(0); i < n; i++ { 73 | useTimeDiff := math.Abs(float64(p2[i].CpuTime - p1[i].CpuTime)) 74 | total.Time += uint64(useTimeDiff) 75 | 76 | vCPUStat := &hyperAPI.VCPUStats{ 77 | Id: uint32(i / 2), 78 | Time: p1[i].CpuTime, 79 | Usage: float32(100*useTimeDiff) / float32(tn), 80 | } 81 | 82 | stats = append(stats, vCPUStat) 83 | } 84 | 85 | total.Usage = float32(100*total.Time) / float32(tn) / float32(n) 86 | 87 | return stats, total, nil 88 | } 89 | 90 | func netStats(d *libvirt.Domain) ([]*hyperAPI.NetStats, error) { 91 | stats := []*hyperAPI.NetStats{} 92 | 93 | ifaces, err := d.ListAllInterfaceAddresses(libvirt.DOMAIN_INTERFACE_ADDRESSES_SRC_ARP) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | for _, iface := range ifaces { 99 | ifaceStats, err := d.InterfaceStats(iface.Name) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | stats = append(stats, &hyperAPI.NetStats{ 105 | Id: iface.Name, 106 | RxBytes: uint64(ifaceStats.RxBytes), 107 | RxPkts: uint64(ifaceStats.RxPackets), 108 | RxErrs: uint64(ifaceStats.RxErrs), 109 | RxDrops: uint64(ifaceStats.RxDrop), 110 | TxBytes: uint64(ifaceStats.TxBytes), 111 | TxPkts: uint64(ifaceStats.TxPackets), 112 | TxErrs: uint64(ifaceStats.TxErrs), 113 | TxDrops: uint64(ifaceStats.TxDrop), 114 | }) 115 | } 116 | 117 | return stats, nil 118 | } 119 | 120 | func diskStats(d *libvirt.Domain) ([]*hyperAPI.DiskStats, *hyperAPI.DiskStats, error) { 121 | stats := []*hyperAPI.DiskStats{} 122 | total := &hyperAPI.DiskStats{} 123 | 124 | r, err := d.GetXMLDesc(libvirt.DOMAIN_XML_MIGRATABLE) 125 | if err != nil { 126 | return nil, nil, err 127 | } 128 | 129 | desc := &DomainDesc{} 130 | 131 | xml.Unmarshal([]byte(r), desc) 132 | 133 | for _, disk := range desc.Devices.Disks { 134 | dStats, err := d.BlockStats(disk.Target.Dev) 135 | if err != nil { 136 | return nil, nil, err 137 | } 138 | 139 | stats = append(stats, &hyperAPI.DiskStats{ 140 | Id: disk.Target.Dev, 141 | RdReqs: uint64(dStats.RdReq), 142 | RdBytes: uint64(dStats.RdBytes), 143 | RdTimes: uint64(dStats.RdTotalTimes), 144 | WrReqs: uint64(dStats.WrReq), 145 | WrBytes: uint64(dStats.WrBytes), 146 | WrTimes: uint64(dStats.WrTotalTimes), 147 | }) 148 | 149 | total.RdReqs += uint64(dStats.RdReq) 150 | total.RdBytes += uint64(dStats.RdBytes) 151 | total.RdTimes += uint64(dStats.RdTotalTimes) 152 | total.WrReqs += uint64(dStats.WrReq) 153 | total.WrBytes += uint64(dStats.WrBytes) 154 | total.WrTimes += uint64(dStats.WrTotalTimes) 155 | } 156 | 157 | return stats, total, nil 158 | } 159 | -------------------------------------------------------------------------------- /pkg/hyper/templates.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "strings" 7 | 8 | hyperAPI "github.com/tcfw/vpc/pkg/api/v1/hyper" 9 | ) 10 | 11 | //GetTemplate converts the VM desc template to a DomainDesc struct 12 | func GetTemplate(vm *hyperAPI.VM) (*DomainDesc, error) { 13 | if strings.TrimSpace(vm.TemplateId)[0] == '<' { 14 | desc := &DomainDesc{} 15 | desc.Name = vm.Id 16 | 17 | if err := xml.Unmarshal([]byte(vm.TemplateId), desc); err != nil { 18 | return nil, fmt.Errorf("failed to marshal desc: %s", err) 19 | } 20 | 21 | return desc, nil 22 | } 23 | 24 | return nil, fmt.Errorf("unknown template reference") 25 | } 26 | -------------------------------------------------------------------------------- /pkg/hyper/xmddesc_test.go: -------------------------------------------------------------------------------- 1 | package hyper 2 | 3 | import ( 4 | "encoding/xml" 5 | "testing" 6 | 7 | "aqwari.net/xml/xmltree" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestMarshalUnmarshal(t *testing.T) { 12 | obj := &DomainDesc{} 13 | sample := []byte(` 14 | vs-1 15 | f5a921ce-eedd-4d99-8a2a-2a98f4470ca8 16 | 17 | 18 | 19 | 20 | 21 | 2097152 22 | 2097152 23 | 2 24 | 25 | hvm 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | destroy 40 | restart 41 | destroy 42 | 43 | 44 | 45 | 46 | 47 | /usr/bin/qemu-system-x86_64 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 |
56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 | 64 |
65 | 66 | 67 | 68 | 69 | 70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 |
87 | 88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |