Merge ~teknoraver/snappy-hwe-snaps/+git/wifi-ap:wizard into ~snappy-hwe-team/snappy-hwe-snaps/+git/wifi-ap:master
- Git
- lp:~teknoraver/snappy-hwe-snaps/+git/wifi-ap
- wizard
- Merge into master
Status: | Merged |
---|---|
Approved by: | Matteo Croce |
Approved revision: | f77adfa785a7725decf7ad373b9cbffbbef7f2b2 |
Merged at revision: | 91a519eda8cc4c17fa76ce34040692332ab0060f |
Proposed branch: | ~teknoraver/snappy-hwe-snaps/+git/wifi-ap:wizard |
Merge into: | ~snappy-hwe-team/snappy-hwe-snaps/+git/wifi-ap:master |
Diff against target: |
452 lines (+244/-32) 6 files modified
cmd/client/cmd_wizard.go (+158/-31) run-tests.sh (+1/-1) tests/main/conf-wizard-auto-nodefaultip/task.yaml (+25/-0) tests/main/conf-wizard-auto-noip/task.yaml (+21/-0) tests/main/conf-wizard-auto-nowifi/task.yaml (+8/-0) tests/main/conf-wizard-auto/task.yaml (+31/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
System Enablement Bot | continuous-integration | Approve | |
Simon Fels | Approve | ||
Matteo Croce (community) | Needs Fixing | ||
Jim Hodapp (community) | code | Needs Fixing | |
Review via email: mp+311026@code.launchpad.net |
Commit message
Description of the change
automatic configuration for wizard
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:41ff7acc928
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Jim Hodapp (jhodapp) wrote : | # |
Please expand the commit message to say a little bit more about what this MR is about.
Some comments inline below.
Simon Fels (morphis) wrote : | # |
This misses unit-tests and spread tests to verify things are working correct.
Matteo Croce (teknoraver) : | # |
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:28499a2d517
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Matteo Croce (teknoraver) wrote : | # |
spread tests are coming
Simon Fels (morphis) : | # |
Matteo Croce (teknoraver) : | # |
Matteo Croce (teknoraver) : | # |
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:6de3902759f
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:6de3902759f
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Simon Fels (morphis) wrote : | # |
Looks really good already but a few comments about the spread tests.
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:6b4044e74bb
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:6b4044e74bb
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:e4ae745a250
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:e4ae745a250
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:30f51612514
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:30f51612514
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Simon Fels (morphis) : | # |
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:59ab1a76d71
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:59ab1a76d71
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Simon Fels (morphis) wrote : | # |
A few last comments in line
Simon Fels (morphis) wrote : | # |
Once CI approves we can merge this one.
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:f77adfa785a
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
System Enablement Bot (system-enablement-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:f77adfa785a
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/cmd/client/cmd_wizard.go b/cmd/client/cmd_wizard.go | |||
2 | index 9a9ebd5..a368c24 100644 | |||
3 | --- a/cmd/client/cmd_wizard.go | |||
4 | +++ b/cmd/client/cmd_wizard.go | |||
5 | @@ -20,15 +20,24 @@ import ( | |||
6 | 20 | "bytes" | 20 | "bytes" |
7 | 21 | "encoding/json" | 21 | "encoding/json" |
8 | 22 | "fmt" | 22 | "fmt" |
9 | 23 | "io/ioutil" | ||
10 | 24 | "net" | 23 | "net" |
11 | 25 | "net/http" | 24 | "net/http" |
12 | 26 | "os" | 25 | "os" |
13 | 26 | "path/filepath" | ||
14 | 27 | "regexp" | 27 | "regexp" |
15 | 28 | "strconv" | 28 | "strconv" |
16 | 29 | "strings" | 29 | "strings" |
17 | 30 | ) | 30 | ) |
18 | 31 | 31 | ||
19 | 32 | type wizardStep func(map[string]string, *bufio.Reader, bool) error | ||
20 | 33 | |||
21 | 34 | // Go stores both IPv4 and IPv6 as [16]byte | ||
22 | 35 | // with IPv4 addresses stored in the end of the buffer | ||
23 | 36 | // in bytes 13..16 | ||
24 | 37 | const ipv4Offset = net.IPv6len - net.IPv4len | ||
25 | 38 | |||
26 | 39 | var defaultIp = net.IPv4(10, 0, 60, 1) | ||
27 | 40 | |||
28 | 32 | // Utility function to read input and strip the trailing \n | 41 | // Utility function to read input and strip the trailing \n |
29 | 33 | func readUserInput(reader *bufio.Reader) string { | 42 | func readUserInput(reader *bufio.Reader) string { |
30 | 34 | ret, err := reader.ReadString('\n') | 43 | ret, err := reader.ReadString('\n') |
31 | @@ -41,15 +50,15 @@ func readUserInput(reader *bufio.Reader) string { | |||
32 | 41 | // Helper function to list network interfaces | 50 | // Helper function to list network interfaces |
33 | 42 | func findExistingInterfaces(wifi bool) (wifis []string) { | 51 | func findExistingInterfaces(wifi bool) (wifis []string) { |
34 | 43 | // Use sysfs to get interfaces list | 52 | // Use sysfs to get interfaces list |
37 | 44 | const sysNetPath = "/sys/class/net/" | 53 | const sysNetPath = "/sys/class/net" |
38 | 45 | ifaces, err := ioutil.ReadDir(sysNetPath) | 54 | ifaces, err := net.Interfaces() |
39 | 46 | wifis = []string{} | 55 | wifis = []string{} |
40 | 47 | if err == nil { | 56 | if err == nil { |
41 | 48 | for _, iface := range ifaces { | 57 | for _, iface := range ifaces { |
43 | 49 | if iface.Name() != "lo" { | 58 | if iface.Flags&net.FlagLoopback == 0 { |
44 | 50 | // The "wireless" subdirectory exists only for wireless interfaces | 59 | // The "wireless" subdirectory exists only for wireless interfaces |
47 | 51 | if _, err := os.Stat(sysNetPath + iface.Name() + "/wireless"); os.IsNotExist(err) != wifi { | 60 | if _, err := os.Stat(filepath.Join(sysNetPath, iface.Name, "wireless")); os.IsNotExist(err) != wifi { |
48 | 52 | wifis = append(wifis, iface.Name()) | 61 | wifis = append(wifis, iface.Name) |
49 | 53 | } | 62 | } |
50 | 54 | } | 63 | } |
51 | 55 | } | 64 | } |
52 | @@ -58,11 +67,39 @@ func findExistingInterfaces(wifi bool) (wifis []string) { | |||
53 | 58 | return | 67 | return |
54 | 59 | } | 68 | } |
55 | 60 | 69 | ||
57 | 61 | type wizardStep func(map[string]string, *bufio.Reader) error | 70 | func findFreeSubnet(startIp net.IP) (net.IP, error) { |
58 | 71 | curIp := startIp | ||
59 | 72 | |||
60 | 73 | addrs, err := net.InterfaceAddrs() | ||
61 | 74 | if err != nil { | ||
62 | 75 | return nil, err | ||
63 | 76 | } | ||
64 | 77 | |||
65 | 78 | // Start from startIp and increment the third octect every time | ||
66 | 79 | // until we found an IP which isn't assigned to any interface | ||
67 | 80 | for found := false; !found; { | ||
68 | 81 | // Cycle through all the assigned addresses | ||
69 | 82 | for _, addr := range addrs { | ||
70 | 83 | found = true | ||
71 | 84 | _, subnet, _ := net.ParseCIDR(addr.String()) | ||
72 | 85 | // If busy, increment the third octect and retry | ||
73 | 86 | if subnet.Contains(curIp) { | ||
74 | 87 | found = false | ||
75 | 88 | if curIp[ipv4Offset+2] == 255 { | ||
76 | 89 | return nil, fmt.Errorf("No free netmask found") | ||
77 | 90 | } | ||
78 | 91 | curIp[ipv4Offset+2]++ | ||
79 | 92 | break | ||
80 | 93 | } | ||
81 | 94 | } | ||
82 | 95 | } | ||
83 | 96 | |||
84 | 97 | return curIp, nil | ||
85 | 98 | } | ||
86 | 62 | 99 | ||
87 | 63 | var allSteps = [...]wizardStep{ | 100 | var allSteps = [...]wizardStep{ |
88 | 64 | // determine the WiFi interface | 101 | // determine the WiFi interface |
90 | 65 | func(configuration map[string]string, reader *bufio.Reader) error { | 102 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
91 | 66 | ifaces := findExistingInterfaces(true) | 103 | ifaces := findExistingInterfaces(true) |
92 | 67 | if len(ifaces) == 0 { | 104 | if len(ifaces) == 0 { |
93 | 68 | return fmt.Errorf("There are no valid wireless network interfaces available") | 105 | return fmt.Errorf("There are no valid wireless network interfaces available") |
94 | @@ -70,12 +107,19 @@ var allSteps = [...]wizardStep{ | |||
95 | 70 | fmt.Println("Automatically selected only available wireless network interface " + ifaces[0]) | 107 | fmt.Println("Automatically selected only available wireless network interface " + ifaces[0]) |
96 | 71 | return nil | 108 | return nil |
97 | 72 | } | 109 | } |
98 | 110 | |||
99 | 111 | if nonInteractive { | ||
100 | 112 | fmt.Println("Selecting interface", ifaces[0]) | ||
101 | 113 | configuration["wifi.interface"] = ifaces[0] | ||
102 | 114 | return nil | ||
103 | 115 | } | ||
104 | 116 | |||
105 | 73 | fmt.Print("Which wireless interface you want to use? ") | 117 | fmt.Print("Which wireless interface you want to use? ") |
106 | 74 | ifacesVerb := "are" | 118 | ifacesVerb := "are" |
107 | 75 | if len(ifaces) == 1 { | 119 | if len(ifaces) == 1 { |
108 | 76 | ifacesVerb = "is" | 120 | ifacesVerb = "is" |
109 | 77 | } | 121 | } |
111 | 78 | fmt.Printf("Available %s %s:", ifacesVerb, strings.Join(ifaces, ", ")) | 122 | fmt.Printf("Available %s %s: ", ifacesVerb, strings.Join(ifaces, ", ")) |
112 | 79 | iface := readUserInput(reader) | 123 | iface := readUserInput(reader) |
113 | 80 | if re := regexp.MustCompile("^[[:alnum:]]+$"); !re.MatchString(iface) { | 124 | if re := regexp.MustCompile("^[[:alnum:]]+$"); !re.MatchString(iface) { |
114 | 81 | return fmt.Errorf("Invalid interface name '%s' given", iface) | 125 | return fmt.Errorf("Invalid interface name '%s' given", iface) |
115 | @@ -86,7 +130,12 @@ var allSteps = [...]wizardStep{ | |||
116 | 86 | }, | 130 | }, |
117 | 87 | 131 | ||
118 | 88 | // Ask for WiFi ESSID | 132 | // Ask for WiFi ESSID |
120 | 89 | func(configuration map[string]string, reader *bufio.Reader) error { | 133 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
121 | 134 | if nonInteractive { | ||
122 | 135 | configuration["wifi.ssid"] = "Ubuntu" | ||
123 | 136 | return nil | ||
124 | 137 | } | ||
125 | 138 | |||
126 | 90 | fmt.Print("Which SSID you want to use for the access point: ") | 139 | fmt.Print("Which SSID you want to use for the access point: ") |
127 | 91 | iface := readUserInput(reader) | 140 | iface := readUserInput(reader) |
128 | 92 | if len(iface) == 0 || len(iface) > 31 { | 141 | if len(iface) == 0 || len(iface) > 31 { |
129 | @@ -98,7 +147,12 @@ var allSteps = [...]wizardStep{ | |||
130 | 98 | }, | 147 | }, |
131 | 99 | 148 | ||
132 | 100 | // Select WiFi encryption type | 149 | // Select WiFi encryption type |
134 | 101 | func(configuration map[string]string, reader *bufio.Reader) error { | 150 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
135 | 151 | if nonInteractive { | ||
136 | 152 | configuration["wifi.security"] = "open" | ||
137 | 153 | return nil | ||
138 | 154 | } | ||
139 | 155 | |||
140 | 102 | fmt.Print("Do you want to protect your network with a WPA2 password instead of staying open for everyone? (y/n) ") | 156 | fmt.Print("Do you want to protect your network with a WPA2 password instead of staying open for everyone? (y/n) ") |
141 | 103 | switch resp := strings.ToLower(readUserInput(reader)); resp { | 157 | switch resp := strings.ToLower(readUserInput(reader)); resp { |
142 | 104 | case "y": | 158 | case "y": |
143 | @@ -113,7 +167,7 @@ var allSteps = [...]wizardStep{ | |||
144 | 113 | }, | 167 | }, |
145 | 114 | 168 | ||
146 | 115 | // If WPA2 is set, ask for valid password | 169 | // If WPA2 is set, ask for valid password |
148 | 116 | func(configuration map[string]string, reader *bufio.Reader) error { | 170 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
149 | 117 | if configuration["wifi.security"] == "open" { | 171 | if configuration["wifi.security"] == "open" { |
150 | 118 | return nil | 172 | return nil |
151 | 119 | } | 173 | } |
152 | @@ -128,7 +182,20 @@ var allSteps = [...]wizardStep{ | |||
153 | 128 | }, | 182 | }, |
154 | 129 | 183 | ||
155 | 130 | // Configure WiFi AP IP address | 184 | // Configure WiFi AP IP address |
157 | 131 | func(configuration map[string]string, reader *bufio.Reader) error { | 185 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
158 | 186 | if nonInteractive { | ||
159 | 187 | wifiIp, err := findFreeSubnet(defaultIp) | ||
160 | 188 | if err != nil { | ||
161 | 189 | return err | ||
162 | 190 | } | ||
163 | 191 | |||
164 | 192 | fmt.Println("AccessPoint IP set to", wifiIp.String()) | ||
165 | 193 | |||
166 | 194 | configuration["wifi.address"] = wifiIp.String() | ||
167 | 195 | configuration["wifi.netmask"] = "255.255.255.0" | ||
168 | 196 | return nil | ||
169 | 197 | } | ||
170 | 198 | |||
171 | 132 | fmt.Print("Insert the Access Point IP address: ") | 199 | fmt.Print("Insert the Access Point IP address: ") |
172 | 133 | inputIp := readUserInput(reader) | 200 | inputIp := readUserInput(reader) |
173 | 134 | ipv4 := net.ParseIP(inputIp) | 201 | ipv4 := net.ParseIP(inputIp) |
174 | @@ -143,21 +210,33 @@ var allSteps = [...]wizardStep{ | |||
175 | 143 | } | 210 | } |
176 | 144 | 211 | ||
177 | 145 | configuration["wifi.address"] = inputIp | 212 | configuration["wifi.address"] = inputIp |
181 | 146 | 213 | configuration["wifi.netmask"] = ipv4.DefaultMask().String() | |
179 | 147 | nmask := ipv4.DefaultMask() | ||
180 | 148 | configuration["wifi.netmask"] = fmt.Sprintf("%d.%d.%d.%d", nmask[0], nmask[1], nmask[2], nmask[3]) | ||
182 | 149 | 214 | ||
183 | 150 | return nil | 215 | return nil |
184 | 151 | }, | 216 | }, |
185 | 152 | 217 | ||
186 | 153 | // Configure the DHCP pool | 218 | // Configure the DHCP pool |
188 | 154 | func(configuration map[string]string, reader *bufio.Reader) error { | 219 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
189 | 220 | if nonInteractive { | ||
190 | 221 | wifiIp := net.ParseIP(configuration["wifi.address"]) | ||
191 | 222 | |||
192 | 223 | // Set the DCHP in the range 2..199 with 198 total hosts | ||
193 | 224 | // leave about 50 hosts outside the pool for static addresses | ||
194 | 225 | // wifiIp[ipv4Offset + 3] is the last octect of the IP address | ||
195 | 226 | wifiIp[ipv4Offset+3] = 2 | ||
196 | 227 | configuration["dhcp.range-start"] = wifiIp.String() | ||
197 | 228 | wifiIp[ipv4Offset+3] = 199 | ||
198 | 229 | configuration["dhcp.range-stop"] = wifiIp.String() | ||
199 | 230 | |||
200 | 231 | return nil | ||
201 | 232 | } | ||
202 | 233 | |||
203 | 155 | var maxpoolsize byte | 234 | var maxpoolsize byte |
204 | 156 | ipv4 := net.ParseIP(configuration["wifi.address"]) | 235 | ipv4 := net.ParseIP(configuration["wifi.address"]) |
207 | 157 | if ipv4[15] <= 128 { | 236 | if ipv4[ipv4Offset+3] <= 128 { |
208 | 158 | maxpoolsize = 254 - ipv4[15] | 237 | maxpoolsize = 254 - ipv4[ipv4Offset+3] |
209 | 159 | } else { | 238 | } else { |
211 | 160 | maxpoolsize = ipv4[15] - 1 | 239 | maxpoolsize = ipv4[ipv4Offset+3] - 1 |
212 | 161 | } | 240 | } |
213 | 162 | 241 | ||
214 | 163 | fmt.Printf("How many host do you want your DHCP pool to hold to? (1-%d) ", maxpoolsize) | 242 | fmt.Printf("How many host do you want your DHCP pool to hold to? (1-%d) ", maxpoolsize) |
215 | @@ -172,19 +251,28 @@ var allSteps = [...]wizardStep{ | |||
216 | 172 | 251 | ||
217 | 173 | nhosts := byte(inputhost) | 252 | nhosts := byte(inputhost) |
218 | 174 | // Allocate the pool in the bigger half, trying to avoid overlap with access point IP | 253 | // Allocate the pool in the bigger half, trying to avoid overlap with access point IP |
222 | 175 | if ipv4[15] <= 128 { | 254 | if octect3 := ipv4[ipv4Offset+3]; octect3 <= 128 { |
223 | 176 | configuration["dhcp.range-start"] = fmt.Sprintf("%d.%d.%d.%d", ipv4[12], ipv4[13], ipv4[14], ipv4[15]+1) | 255 | ipv4[ipv4Offset+3] = octect3 + 1 |
224 | 177 | configuration["dhcp.range-stop"] = fmt.Sprintf("%d.%d.%d.%d", ipv4[12], ipv4[13], ipv4[14], ipv4[15]+nhosts) | 256 | configuration["dhcp.range-start"] = ipv4.String() |
225 | 257 | ipv4[ipv4Offset+3] = octect3 + nhosts | ||
226 | 258 | configuration["dhcp.range-stop"] = ipv4.String() | ||
227 | 178 | } else { | 259 | } else { |
230 | 179 | configuration["dhcp.range-start"] = fmt.Sprintf("%d.%d.%d.%d", ipv4[12], ipv4[13], ipv4[14], ipv4[15]-nhosts) | 260 | ipv4[ipv4Offset+3] = octect3 - nhosts |
231 | 180 | configuration["dhcp.range-stop"] = fmt.Sprintf("%d.%d.%d.%d", ipv4[12], ipv4[13], ipv4[14], ipv4[15]-1) | 261 | configuration["dhcp.range-start"] = ipv4.String() |
232 | 262 | ipv4[ipv4Offset+3] = octect3 - 1 | ||
233 | 263 | configuration["dhcp.range-stop"] = ipv4.String() | ||
234 | 181 | } | 264 | } |
235 | 182 | 265 | ||
236 | 183 | return nil | 266 | return nil |
237 | 184 | }, | 267 | }, |
238 | 185 | 268 | ||
239 | 186 | // Enable or disable connection sharing | 269 | // Enable or disable connection sharing |
241 | 187 | func(configuration map[string]string, reader *bufio.Reader) error { | 270 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
242 | 271 | if nonInteractive { | ||
243 | 272 | configuration["share.disabled"] = "0" | ||
244 | 273 | return nil | ||
245 | 274 | } | ||
246 | 275 | |||
247 | 188 | fmt.Print("Do you want to enable connection sharing? (y/n) ") | 276 | fmt.Print("Do you want to enable connection sharing? (y/n) ") |
248 | 189 | switch resp := strings.ToLower(readUserInput(reader)); resp { | 277 | switch resp := strings.ToLower(readUserInput(reader)); resp { |
249 | 190 | case "y": | 278 | case "y": |
250 | @@ -199,10 +287,39 @@ var allSteps = [...]wizardStep{ | |||
251 | 199 | }, | 287 | }, |
252 | 200 | 288 | ||
253 | 201 | // Select the wired interface to share | 289 | // Select the wired interface to share |
255 | 202 | func(configuration map[string]string, reader *bufio.Reader) error { | 290 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
256 | 291 | if nonInteractive { | ||
257 | 292 | configuration["share.disabled"] = "1" | ||
258 | 293 | |||
259 | 294 | procNetRoute, err := os.Open("/proc/net/route") | ||
260 | 295 | if err != nil { | ||
261 | 296 | return err | ||
262 | 297 | } | ||
263 | 298 | defer procNetRoute.Close() | ||
264 | 299 | |||
265 | 300 | scanner := bufio.NewScanner(procNetRoute) | ||
266 | 301 | // Skip the first line with table header | ||
267 | 302 | scanner.Text() | ||
268 | 303 | for scanner.Scan() { | ||
269 | 304 | route := strings.Fields(scanner.Text()) | ||
270 | 305 | // A /proc/net/route line is in the form: iface destination gateway ... | ||
271 | 306 | // eg. | ||
272 | 307 | // eth1 00000000 0155A8C0 ... | ||
273 | 308 | // look for a 0 destination (0.0.0.0) which is our default route | ||
274 | 309 | if len(route) > 2 && route[1] == "00000000" { | ||
275 | 310 | fmt.Println("Selecting", route[0], "for connection sharing") | ||
276 | 311 | configuration["share.disabled"] = "0" | ||
277 | 312 | configuration["share.network-interface"] = route[0] | ||
278 | 313 | } | ||
279 | 314 | } | ||
280 | 315 | |||
281 | 316 | return nil | ||
282 | 317 | } | ||
283 | 318 | |||
284 | 203 | if configuration["share.disabled"] == "1" { | 319 | if configuration["share.disabled"] == "1" { |
285 | 204 | return nil | 320 | return nil |
286 | 205 | } | 321 | } |
287 | 322 | |||
288 | 206 | ifaces := findExistingInterfaces(false) | 323 | ifaces := findExistingInterfaces(false) |
289 | 207 | if len(ifaces) == 0 { | 324 | if len(ifaces) == 0 { |
290 | 208 | fmt.Println("No network interface available which's connection can be shared. Disabling connection sharing.") | 325 | fmt.Println("No network interface available which's connection can be shared. Disabling connection sharing.") |
291 | @@ -224,7 +341,12 @@ var allSteps = [...]wizardStep{ | |||
292 | 224 | return nil | 341 | return nil |
293 | 225 | }, | 342 | }, |
294 | 226 | 343 | ||
296 | 227 | func (configuration map[string]string, reader *bufio.Reader) error { | 344 | func(configuration map[string]string, reader *bufio.Reader, nonInteractive bool) error { |
297 | 345 | if nonInteractive { | ||
298 | 346 | configuration["disabled"] = "0" | ||
299 | 347 | return nil | ||
300 | 348 | } | ||
301 | 349 | |||
302 | 228 | fmt.Print("Do you want to enable the AP now? (y/n) ") | 350 | fmt.Print("Do you want to enable the AP now? (y/n) ") |
303 | 229 | switch resp := strings.ToLower(readUserInput(reader)); resp { | 351 | switch resp := strings.ToLower(readUserInput(reader)); resp { |
304 | 230 | case "y": | 352 | case "y": |
305 | @@ -262,7 +384,9 @@ func applyConfiguration(configuration map[string]string) error { | |||
306 | 262 | } | 384 | } |
307 | 263 | } | 385 | } |
308 | 264 | 386 | ||
310 | 265 | type wizardCommand struct{} | 387 | type wizardCommand struct { |
311 | 388 | Auto bool `long:"auto" description:"Automatically configure the AP"` | ||
312 | 389 | } | ||
313 | 266 | 390 | ||
314 | 267 | func (cmd *wizardCommand) Execute(args []string) error { | 391 | func (cmd *wizardCommand) Execute(args []string) error { |
315 | 268 | // Setup some sane default values, we don't ask the user for everything | 392 | // Setup some sane default values, we don't ask the user for everything |
316 | @@ -272,9 +396,12 @@ func (cmd *wizardCommand) Execute(args []string) error { | |||
317 | 272 | 396 | ||
318 | 273 | for _, step := range allSteps { | 397 | for _, step := range allSteps { |
319 | 274 | for { | 398 | for { |
321 | 275 | if err := step(configuration, reader); err != nil { | 399 | if err := step(configuration, reader, cmd.Auto); err != nil { |
322 | 400 | if cmd.Auto { | ||
323 | 401 | return err | ||
324 | 402 | } | ||
325 | 276 | fmt.Println("Error: ", err) | 403 | fmt.Println("Error: ", err) |
327 | 277 | fmt.Print("You want to try again? (y/n) ") | 404 | fmt.Print("Do you want to try again? (y/n) ") |
328 | 278 | answer := readUserInput(reader) | 405 | answer := readUserInput(reader) |
329 | 279 | if strings.ToLower(answer) != "y" { | 406 | if strings.ToLower(answer) != "y" { |
330 | 280 | return err | 407 | return err |
331 | diff --git a/run-tests.sh b/run-tests.sh | |||
332 | index eaa2fca..e234fc2 100755 | |||
333 | --- a/run-tests.sh | |||
334 | +++ b/run-tests.sh | |||
335 | @@ -73,7 +73,7 @@ if [ ! -e $SPREAD_QEMU_PATH/$image_name ] || [ $force_new_image -eq 1 ] ; then | |||
336 | 73 | echo "INFO: Creating new qemu test image ..." | 73 | echo "INFO: Creating new qemu test image ..." |
337 | 74 | (cd tests/image ; sudo ./create-image.sh $channel) | 74 | (cd tests/image ; sudo ./create-image.sh $channel) |
338 | 75 | mkdir -p $SPREAD_QEMU_PATH | 75 | mkdir -p $SPREAD_QEMU_PATH |
340 | 76 | mv tests/image/ubuntu-core-16.img $SPREAD_QEMU_PATH/$image_name | 76 | mv -f tests/image/ubuntu-core-16.img $SPREAD_QEMU_PATH/$image_name |
341 | 77 | fi | 77 | fi |
342 | 78 | 78 | ||
343 | 79 | # We currently only run spread tests but we could do other things | 79 | # We currently only run spread tests but we could do other things |
344 | diff --git a/tests/main/conf-wizard-auto-nodefaultip/task.yaml b/tests/main/conf-wizard-auto-nodefaultip/task.yaml | |||
345 | 80 | new file mode 100644 | 80 | new file mode 100644 |
346 | index 0000000..e6d6e06 | |||
347 | --- /dev/null | |||
348 | +++ b/tests/main/conf-wizard-auto-nodefaultip/task.yaml | |||
349 | @@ -0,0 +1,25 @@ | |||
350 | 1 | summary: Verify that the wizard is able to find an unused IP | ||
351 | 2 | |||
352 | 3 | prepare: | | ||
353 | 4 | # Simulate a radio network interfaces | ||
354 | 5 | modprobe mac80211_hwsim radios=1 | ||
355 | 6 | |||
356 | 7 | # Dummy interface to assign an IP to | ||
357 | 8 | modprobe dummy | ||
358 | 9 | |||
359 | 10 | # Use 10.0.60.0/20 to keep 10.0.48.1-10.0.63.254 busy | ||
360 | 11 | # and force the wizard to use the 10.0.64.0/24 | ||
361 | 12 | ifconfig dummy0 10.0.48.2/20 | ||
362 | 13 | |||
363 | 14 | restore: | | ||
364 | 15 | rmmod mac80211_hwsim dummy | ||
365 | 16 | |||
366 | 17 | execute: | | ||
367 | 18 | # Start the automatic wizard, it must fail | ||
368 | 19 | /snap/bin/wifi-ap.setup-wizard wizard --auto | ||
369 | 20 | |||
370 | 21 | # Check for assigned IP on subnet 10.0.64.0/24 | ||
371 | 22 | test "$(/snap/bin/wifi-ap.config get wifi.address)" = 10.0.64.1 | ||
372 | 23 | test "$(/snap/bin/wifi-ap.config get wifi.netmask)" = 255.255.255.0 | ||
373 | 24 | test "$(/snap/bin/wifi-ap.config get dhcp.range-start)" = 10.0.64.2 | ||
374 | 25 | test "$(/snap/bin/wifi-ap.config get dhcp.range-stop)" = 10.0.64.199 | ||
375 | diff --git a/tests/main/conf-wizard-auto-noip/task.yaml b/tests/main/conf-wizard-auto-noip/task.yaml | |||
376 | 0 | new file mode 100644 | 26 | new file mode 100644 |
377 | index 0000000..5a0885c | |||
378 | --- /dev/null | |||
379 | +++ b/tests/main/conf-wizard-auto-noip/task.yaml | |||
380 | @@ -0,0 +1,21 @@ | |||
381 | 1 | summary: Verify that wizard fails when all private subnets are busy | ||
382 | 2 | |||
383 | 3 | prepare: | | ||
384 | 4 | # Simulate a radio network interfaces | ||
385 | 5 | modprobe mac80211_hwsim radios=1 | ||
386 | 6 | |||
387 | 7 | # Dummy interface to assign an IP to | ||
388 | 8 | modprobe dummy | ||
389 | 9 | |||
390 | 10 | # Set an IP with a /8 mask which will waste all usable IP | ||
391 | 11 | ifconfig dummy0 10.0.0.2/8 | ||
392 | 12 | |||
393 | 13 | restore: | | ||
394 | 14 | rmmod mac80211_hwsim dummy | ||
395 | 15 | |||
396 | 16 | execute: | | ||
397 | 17 | # Start the automatic wizard, it must fail | ||
398 | 18 | ! /snap/bin/wifi-ap.setup-wizard wizard --auto | ||
399 | 19 | |||
400 | 20 | # Check for a descriptive error message | ||
401 | 21 | /snap/bin/wifi-ap.setup-wizard wizard --auto 2>&1 |grep 'No free netmask found' | ||
402 | diff --git a/tests/main/conf-wizard-auto-nowifi/task.yaml b/tests/main/conf-wizard-auto-nowifi/task.yaml | |||
403 | 0 | new file mode 100644 | 22 | new file mode 100644 |
404 | index 0000000..18301f1 | |||
405 | --- /dev/null | |||
406 | +++ b/tests/main/conf-wizard-auto-nowifi/task.yaml | |||
407 | @@ -0,0 +1,8 @@ | |||
408 | 1 | summary: Verify that wizard fails when there are no WiFi devices | ||
409 | 2 | |||
410 | 3 | execute: | | ||
411 | 4 | # Start the automatic wizard, it must fail | ||
412 | 5 | ! /snap/bin/wifi-ap.setup-wizard wizard --auto | ||
413 | 6 | |||
414 | 7 | # Check for a descriptive error message | ||
415 | 8 | /snap/bin/wifi-ap.setup-wizard wizard --auto 2>&1 |grep 'There are no valid wireless network interfaces available' | ||
416 | diff --git a/tests/main/conf-wizard-auto/task.yaml b/tests/main/conf-wizard-auto/task.yaml | |||
417 | 0 | new file mode 100644 | 9 | new file mode 100644 |
418 | index 0000000..3d1a2c7 | |||
419 | --- /dev/null | |||
420 | +++ b/tests/main/conf-wizard-auto/task.yaml | |||
421 | @@ -0,0 +1,31 @@ | |||
422 | 1 | summary: Verify that the automatic wizard works | ||
423 | 2 | |||
424 | 3 | prepare: | | ||
425 | 4 | # Simulate two WiFi radio network interfaces | ||
426 | 5 | modprobe mac80211_hwsim radios=2 | ||
427 | 6 | |||
428 | 7 | restore: | | ||
429 | 8 | rmmod mac80211_hwsim | ||
430 | 9 | |||
431 | 10 | execute: | | ||
432 | 11 | # Start the automatic wizard | ||
433 | 12 | /snap/bin/wifi-ap.setup-wizard wizard --auto | ||
434 | 13 | |||
435 | 14 | # Check that we get good default values | ||
436 | 15 | test "$(/snap/bin/wifi-ap.config get disabled)" = 0 | ||
437 | 16 | |||
438 | 17 | test "$(/snap/bin/wifi-ap.config get wifi.interface)" = wlan0 | ||
439 | 18 | test "$(/snap/bin/wifi-ap.config get wifi.security)" = open | ||
440 | 19 | test "$(/snap/bin/wifi-ap.config get wifi.ssid)" = Ubuntu | ||
441 | 20 | test "$(/snap/bin/wifi-ap.config get wifi.address)" = 10.0.60.1 | ||
442 | 21 | |||
443 | 22 | test "$(/snap/bin/wifi-ap.config get dhcp.range-start)" = 10.0.60.2 | ||
444 | 23 | test "$(/snap/bin/wifi-ap.config get dhcp.range-stop)" = 10.0.60.199 | ||
445 | 24 | |||
446 | 25 | default_route=$(ip route |awk '/default/{print$5}') | ||
447 | 26 | if [ -n "$default_route" ]; then | ||
448 | 27 | test "$(/snap/bin/wifi-ap.config get share.disabled)" = 0 | ||
449 | 28 | test "$(/snap/bin/wifi-ap.config get share.network-interface)" = "$default_route" | ||
450 | 29 | else | ||
451 | 30 | test "$(/snap/bin/wifi-ap.config get share.disabled)" = 1 | ||
452 | 31 | fi |
PASSED: Continuous integration, rev:88488cac145 edd53a5f4f74ccf 76da535fb679d3 /jenkins. canonical. com/system- enablement/ job/generic- build-snap/ 305/ /jenkins. canonical. com/system- enablement/ job/generic- update- snap-mp/ 207/console
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild: /jenkins. canonical. com/system- enablement/ job/generic- build-snap/ 305/rebuild
https:/