1package packngo
2
3import (
4	"fmt"
5)
6
7const portBasePath = "/ports"
8
9type NetworkType int
10
11const (
12	NetworkL3 NetworkType = iota
13	NetworkHybrid
14	NetworkL2Bonded
15	NetworkL2Individual
16	NetworkUnknown
17)
18
19// DevicePortService handles operations on a port which belongs to a particular device
20type DevicePortService interface {
21	Assign(*PortAssignRequest) (*Port, *Response, error)
22	Unassign(*PortAssignRequest) (*Port, *Response, error)
23	Bond(*BondRequest) (*Port, *Response, error)
24	Disbond(*DisbondRequest) (*Port, *Response, error)
25	PortToLayerTwo(string) (*Port, *Response, error)
26	PortToLayerThree(string) (*Port, *Response, error)
27	DeviceToLayerTwo(string) (*Device, error)
28	DeviceToLayerThree(string) (*Device, error)
29	DeviceNetworkType(string) (NetworkType, error)
30	GetBondPort(string) (*Port, error)
31	GetPortByName(string, string) (*Port, error)
32}
33
34type PortData struct {
35	MAC    string `json:"mac"`
36	Bonded bool   `json:"bonded"`
37}
38
39type Port struct {
40	ID                      string           `json:"id"`
41	Type                    string           `json:"type"`
42	Name                    string           `json:"name"`
43	Data                    PortData         `json:"data"`
44	AttachedVirtualNetworks []VirtualNetwork `json:"virtual_networks"`
45}
46
47type AddressRequest struct {
48	AddressFamily int  `json:"address_family"`
49	Public        bool `json:"public"`
50}
51
52type BackToL3Request struct {
53	RequestIPs []AddressRequest `json:"request_ips"`
54}
55
56type DevicePortServiceOp struct {
57	client *Client
58}
59
60type PortAssignRequest struct {
61	PortID           string `json:"id"`
62	VirtualNetworkID string `json:"vnid"`
63}
64
65type BondRequest struct {
66	PortID     string `json:"id"`
67	BulkEnable bool   `json:"bulk_enable"`
68}
69
70type DisbondRequest struct {
71	PortID      string `json:"id"`
72	BulkDisable bool   `json:"bulk_disable"`
73}
74
75func (i *DevicePortServiceOp) GetBondPort(deviceID string) (*Port, error) {
76	device, _, err := i.client.Devices.Get(deviceID)
77	if err != nil {
78		return nil, err
79	}
80	for _, port := range device.NetworkPorts {
81		if port.Type == "NetworkBondPort" {
82			return &port, nil
83		}
84	}
85
86	return nil, fmt.Errorf("No bonded port found in device %s", deviceID)
87}
88
89func (i *DevicePortServiceOp) GetPortByName(deviceID, name string) (*Port, error) {
90	device, _, err := i.client.Devices.Get(deviceID)
91	if err != nil {
92		return nil, err
93	}
94	for _, port := range device.NetworkPorts {
95		if port.Name == name {
96			return &port, nil
97		}
98	}
99
100	return nil, fmt.Errorf("Port %s not found in device %s", name, deviceID)
101}
102
103func (i *DevicePortServiceOp) Assign(par *PortAssignRequest) (*Port, *Response, error) {
104	path := fmt.Sprintf("%s/%s/assign", portBasePath, par.PortID)
105	return i.portAction(path, par)
106}
107
108func (i *DevicePortServiceOp) Unassign(par *PortAssignRequest) (*Port, *Response, error) {
109	path := fmt.Sprintf("%s/%s/unassign", portBasePath, par.PortID)
110	return i.portAction(path, par)
111}
112
113func (i *DevicePortServiceOp) Bond(br *BondRequest) (*Port, *Response, error) {
114	path := fmt.Sprintf("%s/%s/bond", portBasePath, br.PortID)
115	return i.portAction(path, br)
116}
117
118func (i *DevicePortServiceOp) Disbond(dr *DisbondRequest) (*Port, *Response, error) {
119	path := fmt.Sprintf("%s/%s/disbond", portBasePath, dr.PortID)
120	return i.portAction(path, dr)
121}
122
123func (i *DevicePortServiceOp) portAction(path string, req interface{}) (*Port, *Response, error) {
124	port := new(Port)
125
126	resp, err := i.client.DoRequest("POST", path, req, port)
127	if err != nil {
128		return nil, resp, err
129	}
130
131	return port, resp, err
132}
133
134func (i *DevicePortServiceOp) PortToLayerTwo(portID string) (*Port, *Response, error) {
135	path := fmt.Sprintf("%s/%s/convert/layer-2", portBasePath, portID)
136	port := new(Port)
137
138	resp, err := i.client.DoRequest("POST", path, nil, port)
139	if err != nil {
140		return nil, resp, err
141	}
142
143	return port, resp, err
144}
145
146func (i *DevicePortServiceOp) PortToLayerThree(portID string) (*Port, *Response, error) {
147	path := fmt.Sprintf("%s/%s/convert/layer-3", portBasePath, portID)
148	port := new(Port)
149
150	req := BackToL3Request{
151		RequestIPs: []AddressRequest{
152			AddressRequest{AddressFamily: 4, Public: true},
153			AddressRequest{AddressFamily: 4, Public: false},
154			AddressRequest{AddressFamily: 6, Public: true},
155		},
156	}
157
158	resp, err := i.client.DoRequest("POST", path, &req, port)
159	if err != nil {
160		return nil, resp, err
161	}
162
163	return port, resp, err
164}
165
166func (i *DevicePortServiceOp) DeviceNetworkType(deviceID string) (NetworkType, error) {
167	d, _, err := i.client.Devices.Get(deviceID)
168	if err != nil {
169		return NetworkUnknown, err
170	}
171	if d.Plan.Slug == "baremetal_0" || d.Plan.Slug == "baremetal_1" {
172		return NetworkL3, nil
173	}
174	if d.Plan.Slug == "baremetal_1e" {
175		return NetworkHybrid, nil
176	}
177	if len(d.NetworkPorts) < 1 {
178		// really?
179		return NetworkL2Individual, nil
180	}
181	if d.NetworkPorts[0].Data.Bonded {
182		if d.NetworkPorts[2].Data.Bonded {
183			for _, ip := range d.Network {
184				if ip.Management {
185					return NetworkL3, nil
186				}
187			}
188			return NetworkL2Bonded, nil
189		} else {
190			return NetworkHybrid, nil
191		}
192	}
193	return NetworkL2Individual, nil
194}
195
196func (i *DevicePortServiceOp) DeviceToLayerThree(deviceID string) (*Device, error) {
197	// hopefull all the VLANs are unassigned at this point
198	bond0, err := i.client.DevicePorts.GetBondPort(deviceID)
199	if err != nil {
200		return nil, err
201	}
202
203	bond0, _, err = i.client.DevicePorts.PortToLayerThree(bond0.ID)
204	if err != nil {
205		return nil, err
206	}
207	d, _, err := i.client.Devices.Get(deviceID)
208	return d, err
209}
210
211// DeviceToLayerTwo converts device to L2 networking. Use bond0 to attach VLAN.
212func (i *DevicePortServiceOp) DeviceToLayerTwo(deviceID string) (*Device, error) {
213	bond0, err := i.client.DevicePorts.GetBondPort(deviceID)
214	if err != nil {
215		return nil, err
216	}
217
218	bond0, _, err = i.client.DevicePorts.PortToLayerTwo(bond0.ID)
219	if err != nil {
220		return nil, err
221	}
222	d, _, err := i.client.Devices.Get(deviceID)
223	return d, err
224
225}
226